From: Marcin Haba Date: Tue, 19 May 2020 17:31:43 +0000 (+0200) Subject: baculum: New user management. LDAP support. Role-based access control. X-Git-Tag: Release-9.6.4~33 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0d56abfb2192a3e3a2e7906a2e8db19373d08b3b;p=thirdparty%2Fbacula.git baculum: New user management. LDAP support. Role-based access control. --- diff --git a/gui/baculum/.gitignore b/gui/baculum/.gitignore index eea02cf02..3ba8c0b61 100644 --- a/gui/baculum/.gitignore +++ b/gui/baculum/.gitignore @@ -3,6 +3,8 @@ protected/runtime/* protected/Web/Logs/* protected/Web/Config/baculum.users protected/Web/Config/hosts.conf +protected/Web/Config/users.conf +protected/Web/Config/roles.conf protected/Web/Config/settings.conf protected/Web/Config/session.dump protected/API/Config/baculum.users diff --git a/gui/baculum/Makefile b/gui/baculum/Makefile index 224d35a96..584279cfc 100644 --- a/gui/baculum/Makefile +++ b/gui/baculum/Makefile @@ -70,7 +70,8 @@ localeweblang = en pl pt ja localeapilang = en pl pt localewebdirsrc = $(datadir)/$(webdir)/Lang localeapidirsrc = $(datadir)/$(apidir)/Lang -localefile = messages.mo +localemofile = messages.mo +localepofile = messages.po excluded_dirs = '.*/\(3rdParty\|tinymce-405\).*' excluded_files = '.*\(\.htaccess\)$$' @@ -117,14 +118,14 @@ prepare_locale: prepare_build for lang in $(localeweblang) ; do \ mkdir -p $(DESTDIR)$(LOCALEDIR)/$$lang/LC_MESSAGES ; \ mkdir -p $(DESTDIR)$(WWWDIR)/$(localewebdirsrc)/$$lang ; \ - install -m 644 $(localewebdirsrc)/$$lang/$(localefile) $(DESTDIR)$(LOCALEDIR)/$$lang/LC_MESSAGES/$(NAME)-web.mo ; \ - ln -s $(DESTDIR)$(LOCALEDIR)/$$lang/LC_MESSAGES/$(NAME)-web.mo $(DESTDIR)$(WWWDIR)/$(localewebdirsrc)/$$lang/$(localefile) ; \ + install -m 644 $(localewebdirsrc)/$$lang/$(localemofile) $(DESTDIR)$(LOCALEDIR)/$$lang/LC_MESSAGES/$(NAME)-web.mo ; \ + ln -s $(DESTDIR)$(LOCALEDIR)/$$lang/LC_MESSAGES/$(NAME)-web.mo $(DESTDIR)$(WWWDIR)/$(localewebdirsrc)/$$lang/$(localemofile) ; \ done for lang in $(localeapilang) ; do \ mkdir -p $(DESTDIR)$(LOCALEDIR)/$$lang/LC_MESSAGES ; \ mkdir -p $(DESTDIR)$(WWWDIR)/$(localeapidirsrc)/$$lang ; \ - install -m 644 $(localeapidirsrc)/$$lang/$(localefile) $(DESTDIR)$(LOCALEDIR)/$$lang/LC_MESSAGES/$(NAME)-api.mo ; \ - ln -s $(DESTDIR)$(LOCALEDIR)/$$lang/LC_MESSAGES/$(NAME)-api.mo $(DESTDIR)$(WWWDIR)/$(localeapidirsrc)/$$lang/$(localefile) ; \ + install -m 644 $(localeapidirsrc)/$$lang/$(localemofile) $(DESTDIR)$(LOCALEDIR)/$$lang/LC_MESSAGES/$(NAME)-api.mo ; \ + ln -s $(DESTDIR)$(LOCALEDIR)/$$lang/LC_MESSAGES/$(NAME)-api.mo $(DESTDIR)$(WWWDIR)/$(localeapidirsrc)/$$lang/$(localemofile) ; \ done prepare_samples: @@ -160,3 +161,11 @@ setup: prepare_samples prepare_check_script sed -i -e "s#%CONFDIR#$(CONFDIR)#g" $(DESTDIR)$(UNITDIR)/$(NAME)-api-lighttpd.service sed -i -e "s#%CONFDIR#$(CONFDIR)#g" $(DESTDIR)$(UNITDIR)/$(NAME)-web-lighttpd.service find $(DESTDIR)/ -type f -name .gitignore -exec rm -f {} \; + +lang_mo: + for lang in $(localeapilang) ; do \ + msgfmt -o $(localeapidirsrc)/$$lang/$(localemofile) $(localeapidirsrc)/$$lang/$(localepofile); \ + done + for lang in $(localeweblang) ; do \ + msgfmt -o $(localewebdirsrc)/$$lang/$(localemofile) $(localewebdirsrc)/$$lang/$(localepofile); \ + done diff --git a/gui/baculum/examples/deb-template/baculum-web-lighttpd.conf b/gui/baculum/examples/deb-template/baculum-web-lighttpd.conf index 0cf6112dd..e79e71f4d 100644 --- a/gui/baculum/examples/deb-template/baculum-web-lighttpd.conf +++ b/gui/baculum/examples/deb-template/baculum-web-lighttpd.conf @@ -82,7 +82,7 @@ fastcgi.server = ( url.rewrite-once = ( "^/themes/(.+)$" => "/themes/$1", "^/assets/(.+)$" => "/assets/$1", - "^/$" => "/index.php?web", + "^/$" => "/index.php/web", "^/(.+)$" => "/index.php/$1" ) diff --git a/gui/baculum/examples/deb/baculum-web-lighttpd.conf b/gui/baculum/examples/deb/baculum-web-lighttpd.conf index 787b436f6..f0cbd871a 100644 --- a/gui/baculum/examples/deb/baculum-web-lighttpd.conf +++ b/gui/baculum/examples/deb/baculum-web-lighttpd.conf @@ -82,7 +82,7 @@ fastcgi.server = ( url.rewrite-once = ( "^/themes/(.+)$" => "/themes/$1", "^/assets/(.+)$" => "/assets/$1", - "^/$" => "/index.php?web", + "^/$" => "/index.php/web", "^/(.+)$" => "/index.php/$1" ) diff --git a/gui/baculum/examples/rpm-template/baculum-web-lighttpd.conf b/gui/baculum/examples/rpm-template/baculum-web-lighttpd.conf index 97bc237c2..0bbf833c3 100644 --- a/gui/baculum/examples/rpm-template/baculum-web-lighttpd.conf +++ b/gui/baculum/examples/rpm-template/baculum-web-lighttpd.conf @@ -82,7 +82,7 @@ fastcgi.server = ( url.rewrite-once = ( "^/themes/(.+)$" => "/themes/$1", "^/assets/(.+)$" => "/assets/$1", - "^/$" => "/index.php?web", + "^/$" => "/index.php/web", "^/(.+)$" => "/index.php/$1" ) diff --git a/gui/baculum/examples/rpm/baculum-web-lighttpd.conf b/gui/baculum/examples/rpm/baculum-web-lighttpd.conf index ee3883456..f02fac68b 100644 --- a/gui/baculum/examples/rpm/baculum-web-lighttpd.conf +++ b/gui/baculum/examples/rpm/baculum-web-lighttpd.conf @@ -82,7 +82,7 @@ fastcgi.server = ( url.rewrite-once = ( "^/themes/(.+)$" => "/themes/$1", "^/assets/(.+)$" => "/assets/$1", - "^/$" => "/index.php?web", + "^/$" => "/index.php/web", "^/(.+)$" => "/index.php/$1" ) diff --git a/gui/baculum/examples/selinux/baculum-web.te b/gui/baculum/examples/selinux/baculum-web.te index a4273eea4..a57e94fb5 100644 --- a/gui/baculum/examples/selinux/baculum-web.te +++ b/gui/baculum/examples/selinux/baculum-web.te @@ -1,9 +1,10 @@ -module baculum-web 1.0.0; +module baculum-web 1.0.1; require { type httpd_t; type unreserved_port_t; type httpd_cache_t; + type ldap_port_t; class tcp_socket { name_bind name_connect }; class dir { search read write create getattr }; class file { read write create }; @@ -12,5 +13,6 @@ require { #============= httpd_t ============== allow httpd_t unreserved_port_t:tcp_socket { name_bind name_connect }; +allow httpd_t ldap_port_t:tcp_socket { name_connect }; allow httpd_t httpd_cache_t:dir { read create }; allow httpd_t httpd_cache_t:file { read write create }; diff --git a/gui/baculum/protected/API/Class/APIConfig.php b/gui/baculum/protected/API/Class/APIConfig.php index 50543c5d8..014ac2c59 100644 --- a/gui/baculum/protected/API/Class/APIConfig.php +++ b/gui/baculum/protected/API/Class/APIConfig.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -35,7 +35,7 @@ class APIConfig extends ConfigFileModule { /** * Default application language */ - const DEFAULT_LANGUAGE = 'en'; + const DEF_LANG = 'en'; /** * API config file path diff --git a/gui/baculum/protected/API/Class/BaculumAPIPage.php b/gui/baculum/protected/API/Class/BaculumAPIPage.php index f6d974464..32b0b401d 100644 --- a/gui/baculum/protected/API/Class/BaculumAPIPage.php +++ b/gui/baculum/protected/API/Class/BaculumAPIPage.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -38,7 +38,7 @@ class BaculumAPIPage extends BaculumPage { public function onPreInit($param) { parent::onPreInit($param); $config = $this->getModule('api_config')->getConfig('api'); - $lang = array_key_exists('lang', $config) ? $config['lang'] : APIConfig::DEFAULT_LANGUAGE; + $lang = array_key_exists('lang', $config) ? $config['lang'] : APIConfig::DEF_LANG; $this->Application->getGlobalization()->Culture = $lang; } } diff --git a/gui/baculum/protected/API/Class/BasicAPIUserConfig.php b/gui/baculum/protected/API/Class/BasicAPIUserConfig.php index 6f040891e..cbe630f44 100644 --- a/gui/baculum/protected/API/Class/BasicAPIUserConfig.php +++ b/gui/baculum/protected/API/Class/BasicAPIUserConfig.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -37,7 +37,8 @@ class BasicAPIUserConfig extends BasicUserConfig { const USERS_FILE_NAME = 'Application.API.Config.baculum'; const USERS_FILE_EXTENSION = '.users'; - protected function getConfigPath() { - return Prado::getPathOfNamespace(self::USERS_FILE_NAME, self::USERS_FILE_EXTENSION); + public function getConfigPath() { + // First check if custom config path is set, if not, then use default users file + return parent::getConfigPath() ?: Prado::getPathOfNamespace(self::USERS_FILE_NAME, self::USERS_FILE_EXTENSION); } } diff --git a/gui/baculum/protected/API/Pages/Panel/APIHome.php b/gui/baculum/protected/API/Pages/Panel/APIHome.php index d0246ff5f..30f9aef3e 100644 --- a/gui/baculum/protected/API/Pages/Panel/APIHome.php +++ b/gui/baculum/protected/API/Pages/Panel/APIHome.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -107,7 +107,7 @@ class APIHome extends BaculumAPIPage { $values[] = "{$oauth2_cfg[$ids[$i]]['client_id']} ({$oauth2_cfg[$ids[$i]]['name']})"; } } elseif ($config['api']['auth_type'] === 'basic') { - $api_user_cfg = $this->getModule('basic_apiuser')->getAllUsers(); + $api_user_cfg = $this->getModule('basic_apiuser')->getUsers(); $values = $ids = array_keys($api_user_cfg); } $this->AuthParamsCombo->DataSource = array_combine($ids, $values); @@ -116,7 +116,7 @@ class APIHome extends BaculumAPIPage { private function getBasicUsers() { $basic_users = array(); - $basic_cfg = $this->getModule('basic_apiuser')->getAllUsers(); + $basic_cfg = $this->getModule('basic_apiuser')->getUsers(); foreach($basic_cfg as $user => $pwd) { $basic_users[] = array('username' => $user); } diff --git a/gui/baculum/protected/API/Pages/Panel/APIInstallWizard.php b/gui/baculum/protected/API/Pages/Panel/APIInstallWizard.php index 99766f82c..20ded02a2 100644 --- a/gui/baculum/protected/API/Pages/Panel/APIInstallWizard.php +++ b/gui/baculum/protected/API/Pages/Panel/APIInstallWizard.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -244,7 +244,7 @@ class APIInstallWizard extends BaculumAPIPage { $cfg_data['api']['auth_type'] = 'oauth2'; } $cfg_data['api']['debug'] = isset($this->config['api']['debug']) ? $this->config['api']['debug'] : "0"; - $cfg_data['api']['lang'] = isset($_SESSION['language']) ? $_SESSION['language'] : APIConfig::DEFAULT_LANGUAGE; + $cfg_data['api']['lang'] = isset($_SESSION['language']) ? $_SESSION['language'] : APIConfig::DEF_LANG; $cfg_data['db']['enabled'] = (integer)($this->DatabaseYes->Checked === true); $cfg_data['db']['type'] = $this->DBType->SelectedValue; $cfg_data['db']['name'] = $this->DBName->Text; diff --git a/gui/baculum/protected/Common/Class/Apr1Md5.php b/gui/baculum/protected/Common/Class/Apr1Md5.php new file mode 100644 index 000000000..c12106bed --- /dev/null +++ b/gui/baculum/protected/Common/Class/Apr1Md5.php @@ -0,0 +1,85 @@ +getModule('crypto')->getRandomString(8); + $len = strlen($password); + $text = sprintf('%s$apr1$%s', $password, $salt); + $bin = pack('H32', md5($password . $salt . $password)); + for ($i = $len; $i > 0; $i -= 16) { + $text .= substr($bin, 0, min(16, $i)); + } + for ($i = $len; $i > 0; $i >>= 1) { + $text .= ($i & 1) ? chr(0) : $password[0]; + } + $bin = pack('H32', md5($text)); + for ($i = 0; $i < 1000; $i++) { + $new = ($i & 1) ? $password : $bin; + if ($i % 3) { + $new .= $salt; + } + if ($i % 7) { + $new .= $password; + } + $new .= ($i & 1) ? $bin : $password; + $bin = pack('H32', md5($new)); + } + $tmp = null; + for ($i = 0; $i < 5; $i++) { + $k = $i + 6; + $j = $i + 12; + if ($j == 16) { + $j = 5; + } + $tmp = $bin[$i] . $bin[$k] . $bin[$j] . $tmp; + } + $tmp = chr(0) . chr(0) . $bin[11] . $tmp; + $str = strrev(substr(base64_encode($tmp), 2)); + $tmp = strtr( + $str, + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', + './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' + ); + return sprintf('$apr1$%s$%s', $salt, $tmp); + } +} +?> diff --git a/gui/baculum/protected/Common/Class/BClientScript.php b/gui/baculum/protected/Common/Class/BClientScript.php index baf6878b5..8d916f4df 100644 --- a/gui/baculum/protected/Common/Class/BClientScript.php +++ b/gui/baculum/protected/Common/Class/BClientScript.php @@ -31,7 +31,7 @@ Prado::using('System.Web.UI.WebControls.TClientScript'); */ class BClientScript extends TClientScript { - const SCRIPTS_VERSION = 6; + const SCRIPTS_VERSION = 7; public function getScriptUrl() { diff --git a/gui/baculum/protected/Common/Class/BCrypt.php b/gui/baculum/protected/Common/Class/BCrypt.php new file mode 100644 index 000000000..2a7d6b806 --- /dev/null +++ b/gui/baculum/protected/Common/Class/BCrypt.php @@ -0,0 +1,62 @@ + + * @category Module + * @package Baculum Common + */ +class BCrypt extends CommonModule { + + // bcrypt uses not standard base64 alphabet + const BCRYPT_BASE64_CODE = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + + // bcrypt cost parameter - number of iterations during hashing + const BCRYPT_COST = 10; + + /** + * Get hashed password using BCrypt algorithm and salt. + * + * @param string $password plain text password + * @return string hashed password + */ + public function crypt($password) { + // Suffle string + $rand_string = str_shuffle(self::BCRYPT_BASE64_CODE); + + // BCrypt salt - 22 characters + $salt_str = substr($rand_string, 0, 22); + + $salt = sprintf( + '$2y$%d$%s$', + self::BCRYPT_COST, + $salt_str + ); + return crypt($password, $salt); + } +} +?> diff --git a/gui/baculum/protected/Common/Class/BaculumPage.php b/gui/baculum/protected/Common/Class/BaculumPage.php index 2cdb8fd10..789310934 100644 --- a/gui/baculum/protected/Common/Class/BaculumPage.php +++ b/gui/baculum/protected/Common/Class/BaculumPage.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -129,27 +129,29 @@ class BaculumPage extends TPage { } /** - * Log in as specific user. + * Get full main URL to log in with user and password. + * It is useful to work with Basic authentication (login or logout for example). * - * Note, usually after this method call there required is using exit() just - * after method execution. Otherwise the HTTP redirection may be canceled on some - * web servers. - * - * @access public * @param string $user user name to log in - * @param string $string plain text user's password - * @return none + * @param string $password plain text user's password + * @return string full login URL */ - public function switchToUser($user, $password) { - $http_protocol = isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) ? 'https' : 'http'; + public function getFullLoginUrl($user, $password) { + $protocol = isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) ? 'https' : 'http'; $host = $_SERVER['SERVER_NAME']; $port = $_SERVER['SERVER_PORT']; - $url_prefix = $this->Application->getModule('url_manager')->getUrlPrefix(); + $url_prefix = $this->getModule('url_manager')->getUrlPrefix(); $url_prefix = str_replace('/index.php', '', $url_prefix); - $location = sprintf("%s://%s:%s@%s:%d%s", $http_protocol, $user, $password, $host, $port, $url_prefix); - - // Log in by header - header("Location: $location"); + $location = sprintf( + '%s://%s:%s@%s:%d%s', + $protocol, + $user, + $password, + $host, + $port, + $url_prefix + ); + return $location; } public function setStyleSheetFiles(){ diff --git a/gui/baculum/protected/Common/Class/BaculumUrlMapping.php b/gui/baculum/protected/Common/Class/BaculumUrlMapping.php index a6f34a035..0c54fd9b9 100644 --- a/gui/baculum/protected/Common/Class/BaculumUrlMapping.php +++ b/gui/baculum/protected/Common/Class/BaculumUrlMapping.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -59,6 +59,20 @@ class BaculumUrlMapping extends TUrlMapping { $this->setServiceUrlManager(); } + /** + * Get all pages for current service. + * Pages are taken directly from configuration file. + * + * @return array all pages for service. + */ + public function getPages() { + $pages = []; + foreach ($this->_patterns as $pattern) { + $pages[] = $pattern->getServiceParameter(); + } + return $pages; + } + private function getServiceID() { $service_id = null; $url = $this->getRequestedUrl(); diff --git a/gui/baculum/protected/Common/Class/BasicUserConfig.php b/gui/baculum/protected/Common/Class/BasicUserConfig.php index ac0bb15fd..ba9119c6f 100644 --- a/gui/baculum/protected/Common/Class/BasicUserConfig.php +++ b/gui/baculum/protected/Common/Class/BasicUserConfig.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -29,7 +29,12 @@ Prado::using('Application.Common.Class.CommonModule'); * @category Module * @package Baculum Common */ -abstract class BasicUserConfig extends CommonModule { +class BasicUserConfig extends CommonModule { + + /** + * Stores user config path. + */ + protected $config_path; /** * User name allowed characters pattern @@ -40,7 +45,19 @@ abstract class BasicUserConfig extends CommonModule { * Get config file path to store users' parameters. * @return string config path */ - abstract protected function getConfigPath(); + public function getConfigPath() { + return $this->config_path; + } + + /** + * Set config file path. + * + * @param string $path path to config file + * @return none + */ + public function setConfigPath($path) { + $this->config_path = $path; + } /** * Save user to users configuration file. @@ -50,38 +67,40 @@ abstract class BasicUserConfig extends CommonModule { * @param string $password user's password * @param boolean $clear_config determine if clear config before save * @param mixed $old_user previous username before change + * @param array $opts setting user options * @return boolean true if user saved successfully, otherwise false */ - public function setUsersConfig($user, $password, $clear_config = false, $old_user = null) { + public function setUsersConfig($user, $password, $clear_config = false, $old_user = null, $opts = []) { if ($clear_config === true) { $this->clearUsersConfig(); } - $all_users = $this->getAllUsers(); - $password = $this->getModule('misc')->getHashedPassword($password); + $all_users = $this->getUsers(); + + $alg = key_exists('hash_alg', $opts) ? $opts['hash_alg'] : null; + $password = $this->getModule('crypto')->getHashedPassword($password, $alg); - $userExists = array_key_exists($user, $all_users); + $user_exists = key_exists($user, $all_users); - if ($userExists === true) { + if ($user_exists === true) { // update user password; $all_users[$user] = $password; } if (!is_null($old_user) && $old_user !== $user) { // delete old username with password from configuration file - if (array_key_exists($old_user, $all_users)) { + if (key_exists($old_user, $all_users)) { unset($all_users[$old_user]); } } // add new user if does not exist - if ($userExists === false) { + if ($user_exists === false) { $all_users[$user] = $password; } - $result = $this->saveUserConfig($all_users); - return $result; + return $this->saveUserConfig($all_users); } /** @@ -90,15 +109,19 @@ abstract class BasicUserConfig extends CommonModule { * and encrypted passwords as values. * * @access public + * @param string $patter regular expression pattern * @return array users/passwords list */ - public function getAllUsers() { - $all_users = array(); + public function getUsers($pattern = '') { + $all_users = []; if ($this->isUsersConfig() === true) { $users = file($this->getConfigPath(), FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); - for($i = 0; $i < count($users); $i++) { if (preg_match("/^(?P\S+)\:(?P\S+)$/", $users[$i], $match) === 1) { + if ($pattern && !fnmatch($pattern, $match['user'])) { + // wildcard pattern doesn't match, skip it + continue; + } $all_users[$match['user']] = $match['hash']; } } @@ -123,7 +146,7 @@ abstract class BasicUserConfig extends CommonModule { $usersToFile = implode("\n", $users); $old_umask = umask(0); umask(0077); - $result = file_put_contents($this->getConfigPath(), $usersToFile) !== false; + $result = file_put_contents($this->getConfigPath(), $usersToFile, LOCK_EX) !== false; umask($old_umask); return $result; } @@ -139,7 +162,7 @@ abstract class BasicUserConfig extends CommonModule { */ public function removeUser($username) { $result = false; - $all_users = $this->getAllUsers(); + $all_users = $this->getUsers(); if (array_key_exists($username, $all_users)) { unset($all_users[$username]); $result = $this->saveUserConfig($all_users); @@ -147,6 +170,23 @@ abstract class BasicUserConfig extends CommonModule { return $result; } + /** + * Remove multiple user from users file. + * + * @param array $usernames user names to remove + * @return boolean true if users removed successfully, otherwise false + */ + public function removeUsers(array $usernames) { + $result = false; + $all_users = $this->getUsers(); + for ($i = 0; $i < count($usernames); $i++) { + if (key_exists($usernames[$i], $all_users)) { + unset($all_users[$usernames[$i]]); + } + } + return $this->saveUserConfig($all_users); + } + /** * Check if users configuration file exists. * @@ -164,7 +204,7 @@ abstract class BasicUserConfig extends CommonModule { * @return boolean true if file cleared successfully, otherwise false */ public function clearUsersConfig() { - $result = file_put_contents($this->getConfigPath(), '') !== false; + $result = file_put_contents($this->getConfigPath(), '', LOCK_EX) !== false; return $result; } } diff --git a/gui/baculum/protected/Common/Class/Crypto.php b/gui/baculum/protected/Common/Class/Crypto.php new file mode 100644 index 000000000..e1bc9516c --- /dev/null +++ b/gui/baculum/protected/Common/Class/Crypto.php @@ -0,0 +1,105 @@ + + * @category Module + * @package Baculum Common + */ +class Crypto extends CommonModule { + + /** + * Supported hash algorithms. + */ + const HASH_ALG_APR1_MD5 = 'apr-md5'; + const HASH_ALG_SHA1 = 'sha1'; + const HASH_ALG_SSHA1 = 'ssha1'; + const HASH_ALG_SHA256 = 'sha256'; + const HASH_ALG_SHA512 = 'sha512'; + const HASH_ALG_BCRYPT = 'bcrypt'; + + /** + * Get (pseudo)random string. + * + * Useful for log out user from HTTP Basic auth by providing random password. + * + * @access public + * @return string random string from range [a-zA-Z0-9] + */ + public function getRandomString($length = null) { + $characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + $rand_string = str_shuffle($characters); + if (is_int($length) && $length <= 62) { + $rand_string = substr($rand_string, 0, $length); + } + return $rand_string; + } + + /** + * Get hashed password to use in web server auth. + * If no hash algorithm given, use APR1-MD5. + * + * @access public + * @param string $password plain text password + * @param string $hash_alg hash algorithm (apr1-md5|sha1) + * @return string hashed password + */ + public function getHashedPassword($password, $hash_alg = null) { + $mod = ''; + switch ($hash_alg) { + case self::HASH_ALG_BCRYPT: { + $mod = 'bcrypt'; + break; + } + case self::HASH_ALG_APR1_MD5: { + $mod = 'apr1md5'; + break; + } + case self::HASH_ALG_SHA1: { + $mod = 'sha1'; + break; + } + case self::HASH_ALG_SSHA1: { + $mod = 'ssha1'; + break; + } + case self::HASH_ALG_SHA256: { + $mod = 'sha256'; + break; + } + case self::HASH_ALG_SHA512: { + $mod = 'sha512'; + break; + } + default: { + $mod = 'apr1md5'; + } + } + return $this->getModule($mod)->crypt($password); + } +} +?> diff --git a/gui/baculum/protected/Common/Class/Interfaces.php b/gui/baculum/protected/Common/Class/Interfaces.php index b397a2e4f..62dfa9002 100644 --- a/gui/baculum/protected/Common/Class/Interfaces.php +++ b/gui/baculum/protected/Common/Class/Interfaces.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -65,4 +65,14 @@ interface AuthModule { public function getRequestHeaderValue($header); } + +/** + * Defined user manager methods. + */ +interface UserManager { + + public function init($config); + + public function validateUser($username, $password); +} ?> diff --git a/gui/baculum/protected/Common/Class/Ldap.php b/gui/baculum/protected/Common/Class/Ldap.php new file mode 100644 index 000000000..0a55f50fc --- /dev/null +++ b/gui/baculum/protected/Common/Class/Ldap.php @@ -0,0 +1,304 @@ + + * @category Module + * @package Baculum Common + */ +class Ldap extends CommonModule { + + /** + * Authentication methods. + */ + const AUTH_METHOD_ANON = 'anonymous'; + const AUTH_METHOD_SIMPLE = 'simple'; + + /** + * LDAP protocol types. + */ + const PROTOCOL_PLAIN = 'ldap'; + const PROTOCOL_SSL = 'ldaps'; + + /** + * LDAP attributes. + */ + const DN_ATTR = 'dn'; + + /** + * Stores LDAP connection parameters. + */ + private $params = []; + + /** + * Stores last error message if error occurs. + */ + private $error = ''; + + /** + * Stores obligatory (required) parameters. + */ + private $req_params = array( + 'address', + 'port', + 'protocol_ver', + 'auth_method' + ); + + /** + * Stores LDAP connection resource. + */ + private $conn; + + /** + * Connect to LDAP server. + * Note, it is used to initialize connection parameters, but itself it + * doesn't open any LDAP conection. + * + * @return resource|false LDAP connection resource or false if any error occurs + */ + public function connect() { + $ldapuri = $this->getLdapUri(); + $conn = @ldap_connect($ldapuri); + $this->conn = $conn; + if ($conn) { + $this->setConnectionOpts(); + } else { + $this->setLdapError(); + } + return $conn; + } + + /** + * Binds to LDAP directory. + * Supported are anonymous bind and bind with credentials (RDN/password). + * Note, if an error occurs, the error message is available in + * the error property. + * + * @param string $rdn distinguished name + * @param string $password user password + * @return boolean true on successfull connection, false otherwise + */ + public function bind($rdn = null, $password = null) { + $success = false; + if ($this->conn) { + $success = @ldap_bind($this->conn, $rdn, $password); + } + if (!$success) { + $this->setLdapError(); + } + return $success; + } + + /** + * Set LDAP parameters (both connection and attributes' parameters). + * Parameters are set after successful validation. + * + * @param array $param parameters to work with LDAP + * @return none + */ + public function setParams(array $params) { + if ($this->validateConnectionParams($params)) { + $this->params = $params; + } + } + + /** + * Set LDAP connection specific options. + * + * @return none + */ + public function setConnectionOpts() { + ldap_set_option($this->conn, LDAP_OPT_PROTOCOL_VERSION, $this->params['protocol_ver']); + ldap_set_option($this->conn, LDAP_OPT_REFERRALS, 0); + } + + /** + * Administrative bind action. + * This method should be used always to connect to LDAP (anonymous or with credentials). + * Note, if an error occurs, the error message is available in + * the error property. + * + * @return boolean true on successfull connection, false otherwise + */ + public function adminBind() { + $success = false; + if ($this->connect()) { + if ($this->params['auth_method'] === self::AUTH_METHOD_ANON) { + $success = $this->bind(); + } elseif ($this->params['auth_method'] === self::AUTH_METHOD_SIMPLE) { + $success = $this->bind($this->params['bind_dn'], $this->params['bind_password']); + } + } + return $success; + } + + /** + * Login with username and password. + * Note, if an error occurs, the error message is available in + * the error property. + * + * @param string $username username to login + * @param string $password password to login + * @return boolean true if login finished successfully, false otherwise + */ + public function login($username, $password) { + $success = false; + if ($this->adminBind()) { + $filter = sprintf('(%s=%s)', $this->params['user_attr'], $username); + $user = $this->search($this->params['base_dn'], $filter); + $cnt = ldap_count_entries($this->conn, $user); + if ($cnt === 1) { + $entry = ldap_first_entry($this->conn, $user); + if ($entry) { + $user_dn = ldap_get_dn($this->conn, $entry); + $success = $this->bind($user_dn, $password); + } + } + } + return $success; + } + + /** + * Search in LDAP directory using filters. + * Note, if an error occurs, the error message is available in + * the error property. + * + * @param string $base_dn base distinguished name + * @param string $filter the search filter, ex. '(objectClass=inetOrgPerson)' + * @param array $attr required object attributes + * @return resource search result identifier or false otherwise + */ + public function search($base_dn, $filter, $attr = array('*')) { + $search = @ldap_search($this->conn, $base_dn, $filter, $attr, 0, 0, 0); + if (!$search) { + $this->setLdapError(); + } + return $search; + } + + /** + * Find and get users by filter and attributes. + * Note, if an error occurs, the error message is available in + * the error property. + * + * @param string $filter the search filter, ex. '(objectClass=inetOrgPerson)' + * @param array $attr required object attributes + */ + public function findUserAttr($filter, $attr = array('*')) { + $user_attr = []; + if ($this->adminBind()) { + $search = $this->search($this->params['base_dn'], $filter, $attr); + if ($search) { + $user_attr = ldap_get_entries($this->conn, $search); + } + } + return $user_attr; + } + + /** + * Get LDAP URI used to connecting to LDAP server. + * + * @return string full LDAP URI (ex. ldap://host:port) + */ + public function getLdapUri() { + $protocol = self::PROTOCOL_PLAIN; + if (key_exists('ldaps', $this->params) && $this->params['ldaps'] === 1) { + $protocol = self::PROTOCOL_SSL; + } + return sprintf( + '%s://%s:%d', + $protocol, + $this->params['address'], + $this->params['port'] + ); + } + + /** + * Set LDAP errors in error property. + * Used to get the most information from LDAP server. + * + * @return none + */ + public function setLdapError() { + ldap_get_option($this->conn, LDAP_OPT_DIAGNOSTIC_MESSAGE, $error); + $emsg = sprintf( + 'Error: %d: %s. %s', + ldap_errno($this->conn), + ldap_error($this->conn), + $error + ); + $this->error = $emsg; + $this->getModule('logging')->log( + __FUNCTION__, + $emsg, + Logging::CATEGORY_EXTERNAL, + __FILE__, + __LINE__ + ); + } + + /** + * Get LDAP error message. + * No error is represented by empty string. + * + * @return string error message + */ + public function getLdapError() { + return $this->error; + } + + /** + * Validate LDAP connection parameters. + * + * @param array $params connection parameters + * @return false if any of parameters is invalid + */ + public function validateConnectionParams(array $params) { + $valid = true; + for ($i = 0; $i < count($this->req_params); $i++) { + if (!key_exists($this->req_params[$i], $params)) { + $valid = false; + } + } + return $valid; + } + + /** + * Get simple key/value filter. + * Filters are used to search on LDAP server. + * + * @param string $key search key + * @param string $value search value + * @return formatted string in search filter + */ + public function getFilter($key, $value) { + return sprintf('(%s=%s)', $key, $value); + } +} +?> diff --git a/gui/baculum/protected/Common/Class/Miscellaneous.php b/gui/baculum/protected/Common/Class/Miscellaneous.php index 530edcc63..73c64222f 100644 --- a/gui/baculum/protected/Common/Class/Miscellaneous.php +++ b/gui/baculum/protected/Common/Class/Miscellaneous.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -20,6 +20,15 @@ * Bacula(R) is a registered trademark of Kern Sibbald. */ + +/** + * Module with miscellaneous tools. + * Targetly it is meant to remove after splitting into smaller modules. + * + * @author Marcin Haba + * @category Module + * @package Baculum Common + */ class Miscellaneous extends TModule { const RPATH_PATTERN = '/^b2\d+$/'; @@ -289,75 +298,5 @@ class Miscellaneous extends TModule { } return $jobid; } - - /** - * Get (pseudo)random string. - * - * Useful for log out user from HTTP Basic auth by providing random password. - * - * @access public - * @return string random 62 characters string from range [a-zA-Z0-9] - */ - public function getRandomString($length = null) { - $characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - $rand_string = str_shuffle($characters); - if (is_int($length) && $length <= 62) { - $rand_string = substr($rand_string, 0, $length); - } - return $rand_string; - } - - /** - * Get hashed password to use in web server auth. - * - * @access public - * @param string $password plain text password - * @return string hashed password - */ - public function getHashedPassword($password) { - return $this->cryptApr1Md5($password); - } - - public function cryptApr1Md5($password) { - $salt = $this->getRandomString(8); - $len = strlen($password); - $text = sprintf('%s$apr1$%s', $password, $salt); - $bin = pack('H32', md5($password . $salt . $password)); - for ($i = $len; $i > 0; $i -= 16) { - $text .= substr($bin, 0, min(16, $i)); - } - for ($i = $len; $i > 0; $i >>= 1) { - $text .= ($i & 1) ? chr(0) : $password[0]; - } - $bin = pack('H32', md5($text)); - for ($i = 0; $i < 1000; $i++) { - $new = ($i & 1) ? $password : $bin; - if ($i % 3) { - $new .= $salt; - } - if ($i % 7) { - $new .= $password; - } - $new .= ($i & 1) ? $bin : $password; - $bin = pack('H32', md5($new)); - } - $tmp = null; - for ($i = 0; $i < 5; $i++) { - $k = $i + 6; - $j = $i + 12; - if ($j == 16) { - $j = 5; - } - $tmp = $bin[$i] . $bin[$k] . $bin[$j] . $tmp; - } - $tmp = chr(0) . chr(0) . $bin[11] . $tmp; - $str = strrev(substr(base64_encode($tmp), 2)); - $tmp = strtr( - $str, - 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', - './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' - ); - return sprintf('$apr1$%s$%s', $salt, $tmp); - } } ?> diff --git a/gui/baculum/protected/Common/Class/SessionRecord.php b/gui/baculum/protected/Common/Class/SessionRecord.php index 468a9ce70..e3378b3a3 100644 --- a/gui/baculum/protected/Common/Class/SessionRecord.php +++ b/gui/baculum/protected/Common/Class/SessionRecord.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -32,6 +32,8 @@ Prado::using('Application.Common.Class.Interfaces'); */ class SessionRecord extends CommonModule implements SessionItem { + const SESS_FILE_PERM = 0600; + private static $lock = false; private static $queue = 0; @@ -46,8 +48,18 @@ class SessionRecord extends CommonModule implements SessionItem { private static function store($wouldblock = true) { $c = get_called_class(); $sessfile = $c::getSessionFile(); - if (array_key_exists('sess', $GLOBALS)) { + if (key_exists('sess', $GLOBALS)) { $content = serialize($GLOBALS['sess']); + if (file_exists($sessfile)) { + $perm = (fileperms($sessfile) & 0777); + if ($perm !== self::SESS_FILE_PERM) { + // Correct permissions to more restrictive if needed + chmod($sessfile, self::SESS_FILE_PERM); + } + } + $old_umask = umask(0); + $new_umask = (~(self::SESS_FILE_PERM) & 0777); + umask($new_umask); $fp = fopen($sessfile, 'w'); if (flock($fp, LOCK_EX, $wouldblock)) { fwrite($fp, $content); @@ -65,6 +77,7 @@ class SessionRecord extends CommonModule implements SessionItem { ); } fclose($fp); + umask($old_umask); } } diff --git a/gui/baculum/protected/Common/Class/Sha1.php b/gui/baculum/protected/Common/Class/Sha1.php new file mode 100644 index 000000000..1ce829ef2 --- /dev/null +++ b/gui/baculum/protected/Common/Class/Sha1.php @@ -0,0 +1,49 @@ + + * @category Module + * @package Baculum Common + */ +class Sha1 extends CommonModule { + + /** + * Get hashed password using SHA-1 algorithm. + * + * @param string $password plain text password + * @return string hashed password + */ + public function crypt($password) { + $hash = sha1($password, true); + $bh = base64_encode($hash); + $ret = '{SHA}' . $bh; + return $ret; + } + +} +?> diff --git a/gui/baculum/protected/Common/Class/Sha256.php b/gui/baculum/protected/Common/Class/Sha256.php new file mode 100644 index 000000000..631040558 --- /dev/null +++ b/gui/baculum/protected/Common/Class/Sha256.php @@ -0,0 +1,55 @@ + + * @category Module + * @package Baculum Common + */ +class Sha256 extends CommonModule { + + const SHA256_ROUNDS = 10000; + + /** + * Get hashed password using SHA-256 algorithm and salt. + * + * @param string $password plain text password + * @return string hashed password + */ + public function crypt($password) { + // Salt string - 16 characters for SHA-256 + $salt_str = $this->getModule('crypto')->getRandomString(16); + + $salt = sprintf( + '$5$rounds=%d$%s$', + self::SHA256_ROUNDS, + $salt_str + ); + return crypt($password, $salt); + } +} +?> diff --git a/gui/baculum/protected/Common/Class/Sha512.php b/gui/baculum/protected/Common/Class/Sha512.php new file mode 100644 index 000000000..85fcfeca8 --- /dev/null +++ b/gui/baculum/protected/Common/Class/Sha512.php @@ -0,0 +1,55 @@ + + * @category Module + * @package Baculum Common + */ +class Sha512 extends CommonModule { + + const SHA512_ROUNDS = 10000; + + /** + * Get hashed password using SHA-512 algorithm and salt. + * + * @param string $password plain text password + * @return string hashed password + */ + public function crypt($password) { + // Salt string - 16 characters for SHA-512 + $salt_str = $this->getModule('crypto')->getRandomString(16); + + $salt = sprintf( + '$6$rounds=%d$%s$', + self::SHA512_ROUNDS, + $salt_str + ); + return crypt($password, $salt); + } +} +?> diff --git a/gui/baculum/protected/Common/Class/Ssha1.php b/gui/baculum/protected/Common/Class/Ssha1.php new file mode 100644 index 000000000..3b617f9ba --- /dev/null +++ b/gui/baculum/protected/Common/Class/Ssha1.php @@ -0,0 +1,54 @@ + + * @category Module + * @package Baculum Common + */ +class Ssha1 extends CommonModule { + + /** + * Get hashed password using SHA-1 algorithm and salt. + * + * @param string $password plain text password + * @param string $salt cryptographic salt + * @return string hashed password + */ + public function crypt($password) { + // Salt string - 16 characters for SHA-256 + $salt = $this->getModule('crypto')->getRandomString(4); + + $hash = sha1($password . $salt, true); + $bh = base64_encode($hash . $salt); + $ret = '{SSHA}' . $bh; + return $ret; + } + +} +?> diff --git a/gui/baculum/protected/Common/Portlets/NewAuthClient.php b/gui/baculum/protected/Common/Portlets/NewAuthClient.php index 6c1cb55ec..212300ad3 100644 --- a/gui/baculum/protected/Common/Portlets/NewAuthClient.php +++ b/gui/baculum/protected/Common/Portlets/NewAuthClient.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -51,7 +51,7 @@ class NewAuthClient extends PortletTemplate { $exists = false; $config = $this->getModule('api_config')->getConfig(); if ($this->getAuthType() === 'basic') { - $users = $this->getModule('basic_apiuser')->getAllUsers(); + $users = $this->getModule('basic_apiuser')->getUsers(); if (!key_exists($this->APIBasicLogin->Text, $users)) { $result = $this->getModule('basic_apiuser')->setUsersConfig( $this->APIBasicLogin->Text, diff --git a/gui/baculum/protected/Web/Class/BaculumAPIClient.php b/gui/baculum/protected/Web/Class/BaculumAPIClient.php index bddde48da..9501bb65e 100644 --- a/gui/baculum/protected/Web/Class/BaculumAPIClient.php +++ b/gui/baculum/protected/Web/Class/BaculumAPIClient.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -291,11 +291,7 @@ class BaculumAPIClient extends WebModule { $cached = null; $ret = null; if (is_null($host)) { - if (isset($_SESSION['api_host'])) { - $host = $_SESSION['api_host']; - } else { - $host = HostConfig::MAIN_CATALOG_HOST; - } + $host = $this->User->getAPIHosts(); } if ($use_cache === true) { $cached = $this->getSessionCache($host, $params); @@ -336,11 +332,7 @@ class BaculumAPIClient extends WebModule { */ public function set(array $params, array $options = array(), $host = null, $show_error = true) { if (is_null($host)) { - if (isset($_SESSION['api_host'])) { - $host = $_SESSION['api_host']; - } else { - $host = HostConfig::MAIN_CATALOG_HOST; - } + $host = $this->User->getAPIHosts(); } $host_cfg = $this->getHostParams($host); $uri = $this->getURIResource($host, $params); @@ -377,11 +369,7 @@ class BaculumAPIClient extends WebModule { */ public function create(array $params, array $options, $host = null, $show_error = true) { if (is_null($host)) { - if (isset($_SESSION['api_host'])) { - $host = $_SESSION['api_host']; - } else { - $host = HostConfig::MAIN_CATALOG_HOST; - } + $host = $this->User->getAPIHosts(); } $host_cfg = $this->getHostParams($host); $uri = $this->getURIResource($host, $params); @@ -413,11 +401,7 @@ class BaculumAPIClient extends WebModule { */ public function remove(array $params, $host = null, $show_error = true) { if (is_null($host)) { - if (isset($_SESSION['api_host'])) { - $host = $_SESSION['api_host']; - } else { - $host = HostConfig::MAIN_CATALOG_HOST; - } + $host = $this->User->getAPIHosts(); } $host_cfg = $this->getHostParams($host); $uri = $this->getURIResource($host, $params); @@ -579,7 +563,7 @@ class BaculumAPIClient extends WebModule { private function authToHost($host, $host_cfg) { if (count($host_cfg) > 0 && $host_cfg['auth_type'] === 'oauth2') { - $state = $this->getModule('misc')->getRandomString(16); + $state = $this->getModule('crypto')->getRandomString(16); $params = array( 'response_type' => 'code', 'client_id' => $host_cfg['client_id'], diff --git a/gui/baculum/protected/Web/Class/BaculumWebPage.php b/gui/baculum/protected/Web/Class/BaculumWebPage.php index 75449e8ea..29121095f 100644 --- a/gui/baculum/protected/Web/Class/BaculumWebPage.php +++ b/gui/baculum/protected/Web/Class/BaculumWebPage.php @@ -20,12 +20,11 @@ * Bacula(R) is a registered trademark of Kern Sibbald. */ -session_start(); - Prado::using('Application.Web.Pages.Requirements'); Prado::using('Application.Common.Class.BaculumPage'); Prado::using('Application.Web.Init'); Prado::using('Application.Web.Class.WebConfig'); +Prado::using('Application.Web.Class.PageCategory'); /** * Baculum Web page module. @@ -47,50 +46,19 @@ class BaculumWebPage extends BaculumPage { public function onPreInit($param) { parent::onPreInit($param); $this->web_config = $this->getModule('web_config')->getConfig(); - $this->Application->getGlobalization()->Culture = $this->getLanguage(); if (count($this->web_config) === 0) { - if (isset($_SERVER['PHP_AUTH_USER'])) { - if ($this->Service->getRequestedPagePath() != 'WebConfigWizard') { - $this->goToPage('WebConfigWizard'); - } - // without config there is no way to call api below - return; - } else { - self::accessDenied(); + if ($this->Service->getRequestedPagePath() != 'WebConfigWizard') { + $this->goToPage('WebConfigWizard'); } + // without config there is no way to call api below + return; } Logging::$debug_enabled = (isset($this->web_config['baculum']['debug']) && $this->web_config['baculum']['debug'] == 1); if (!$this->IsPostBack && !$this->IsCallBack) { + $this->postInitActions(); $this->getModule('api')->initSessionCache(true); $this->setSessionUserVars(); } - $this->checkPrivileges(); - } - - /** - * Get curently set language short name (for example: en, pl). - * If language short name is not set in session then the language value - * is taken from Baculum config file, saved in session and returned. - * If the language setting is set in session, then the value from - * session is returned. - * - * @access public - * @return string currently set language short name - */ - public function getLanguage() { - $language = null; - if (isset($_SESSION['language']) && !empty($_SESSION['language'])) { - $language = $_SESSION['language']; - } else { - if (isset($this->web_config['baculum']) && key_exists('lang', $this->web_config['baculum'])) { - $language = $this->web_config['baculum']['lang']; - } - if (is_null($language)) { - $language = WebConfig::DEFAULT_LANGUAGE; - } - $_SESSION['language'] = $language; - } - return $language; } /** @@ -99,24 +67,10 @@ class BaculumWebPage extends BaculumPage { * @return none */ private function setSessionUserVars() { - // NOTE. For oauth2 callback, the PHP_AUTH_USER is empty because no user/pass. - if (count($this->web_config) > 0 && isset($_SERVER['PHP_AUTH_USER'])) { - // Set administrator role - $_SESSION['admin'] = ($_SERVER['PHP_AUTH_USER'] === $this->web_config['baculum']['login']); - - // Set api host for normal user - if (!$_SESSION['admin'] && key_exists('users', $this->web_config) && array_key_exists($_SERVER['PHP_AUTH_USER'], $this->web_config['users'])) { - $_SESSION['api_host'] = $this->web_config['users'][$_SERVER['PHP_AUTH_USER']]; - } elseif ($_SESSION['admin']) { - $_SESSION['api_host'] = 'Main'; - } - } else { - $_SESSION['admin'] = false; - } - // Set director $directors = $this->getModule('api')->get(array('directors'), null, false); - if ($directors->error === 0 && count($directors->output) > 0 && (!key_exists('director', $_SESSION) || $directors->output[0] != $_SESSION['director'])) { + if ($directors->error === 0 && count($directors->output) > 0 && + (!key_exists('director', $_SESSION) || $directors->output[0] != $_SESSION['director'])) { $_SESSION['director'] = $directors->output[0]; } @@ -133,14 +87,72 @@ class BaculumWebPage extends BaculumPage { } } - private function checkPrivileges() { - if (property_exists($this, 'admin') && $this->admin === true && !$_SESSION['admin']) { - self::accessDenied(); + /** + * Redirection to default page defined in application config. + * + * @access public + * @param array $params HTTP GET method parameters in associative array + * @return none + */ + public function goToDefaultPage($params = null) { + $def_page = $this->Service->DefaultPage; + $manager = $this->getModule('users'); + if (!$manager->isPageAllowed($this->User, $this->Service->DefaultPage)) { + // User hasn't access to default service page. Get first allowed page. + $def_page = $this->findDefaultPageForUser(); + + /** + * If page different than default for service, reset params because + * they will not work with different page. + */ + $params = null; } + if (!is_string($def_page)) { + $def_page = $this->getModule('auth')->getLoginPage(); + } + $this->goToPage($def_page, $params); } - public static function accessDenied() { - die('Access denied'); + /** + * Find default page for an user. + * Useful to determine on which page direct user. It takes first one found + * that can be accessible by the user. + * + * @return mixed page path or null if no page for user found + */ + private function findDefaultPageForUser() { + $manager = $this->getModule('users'); + $user_role = $this->getModule('user_role'); + $roles = $this->User->getRoles(); + $pages = []; + for ($i = 0; $i < count($roles); $i++) { + $rpages = $user_role->getPagesByRole($roles[$i]); + for ($j = 0; $j < count($rpages); $j++) { + if (!in_array($rpages[$i], $pages) && $manager->isPageAllowed($this->User, $rpages[$i])) { + $pages[] = $rpages[$i]; + } + } + } + return array_shift($pages); + } + + /** + * Common actions which has to be done for each web page just after + * page pre-loading. + * + * @return none + */ + private function postInitActions() { + /** + * If users config file doesn't exist, create it and populate + * using basic users file. + * Basic auth method is the main Baculum Web auth method. Before introducing + * users.conf file, it was the only one supported method. + */ + $result = $this->getModule('user_config')->importUsers(); + if ($result) { + $this->goToDefaultPage(); + } } } ?> diff --git a/gui/baculum/protected/Web/Class/BasicWebUserConfig.php b/gui/baculum/protected/Web/Class/BasicWebUserConfig.php index 2e622f651..52ff2d630 100644 --- a/gui/baculum/protected/Web/Class/BasicWebUserConfig.php +++ b/gui/baculum/protected/Web/Class/BasicWebUserConfig.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -37,7 +37,9 @@ class BasicWebUserConfig extends BasicUserConfig { const USERS_FILE_NAME = 'Application.Web.Config.baculum'; const USERS_FILE_EXTENSION = '.users'; - protected function getConfigPath() { - return Prado::getPathOfNamespace(self::USERS_FILE_NAME, self::USERS_FILE_EXTENSION); + public function getConfigPath() { + // First check if custom config path is set, if not, then use default users file + return parent::getConfigPath() ?: Prado::getPathOfNamespace(self::USERS_FILE_NAME, self::USERS_FILE_EXTENSION); } } +?> diff --git a/gui/baculum/protected/Web/Class/PageCategory.php b/gui/baculum/protected/Web/Class/PageCategory.php new file mode 100644 index 000000000..6f3976f2a --- /dev/null +++ b/gui/baculum/protected/Web/Class/PageCategory.php @@ -0,0 +1,121 @@ + + * @category Module + * @package Baculum Web + */ +class PageCategory extends WebModule { + + /** + * Pages allowed for user with native role 'normal' + */ + const DASHBOARD = 'Dashboard'; + const JOB_HISTORY_LIST = 'JobHistoryList'; + const JOB_HISTORY_VIEW = 'JobHistoryView'; + const JOB_LIST = 'JobList'; + const JOB_VIEW = 'JobView'; + const CLIENT_LIST = 'ClientList'; + const CLIENT_VIEW = 'ClientView'; + const RESTORE_WIZARD = 'RestoreWizard'; + const GRAPHS = 'Graphs'; + + /** + * System pages - always allowed for authenticated users + */ + const MONITOR = 'Monitor'; + const BACULUM_ERROR = 'BaculumError'; + + /** + * Public pages - always allowed + */ + const LOGIN_PAGE = 'LoginPage'; + const OAUTH2_REDIRECT = 'OAuth2Redirect'; + + /** + * Pages available under conditions. + */ + const WEB_CONFIG_WIZARD = 'WebConfigWizard'; + + /** + * Get page categories. + * + * @return array page categories + */ + public function getCategories($with_sys_pub_pages = true) { + $pages = $this->getModule('url_manager')->getPages(); + //some pages are double because they are defined for access by name or id, so do unique() + $pages = array_unique($pages); + if (!$with_sys_pub_pages) { + $system_pages = $this->getSystemCategories(); + $public_pages = $this->getPublicCategories(); + $pages = array_diff($pages, $system_pages, $public_pages); + } + return $pages; + } + + public function isCategorySystem($category) { + $system_cats = $this->getSystemCategories(); + return in_array($category, $system_cats); + } + + private function getSystemCategories() { + return [ + self::MONITOR, + self::BACULUM_ERROR + ]; + } + + public function isCategoryPublic($category) { + $public_cats = $this->getPublicCategories(); + return in_array($category, $public_cats); + } + + private function getPublicCategories() { + return [ + self::OAUTH2_REDIRECT, + self::LOGIN_PAGE + ]; + } + + public function isCategoryConditional($category) { + $cond_cats = $this->getConditionalCategories(); + return in_array($category, $cond_cats); + } + + private function getConditionalCategories() { + $cond_cats = []; + $host_config = $this->getModule('host_config')->getConfig(); + $first_run = (count($host_config) == 0 || !key_exists(HostConfig::MAIN_CATALOG_HOST, $host_config)); + if ($first_run) { + $cond_cats[] = self::WEB_CONFIG_WIZARD; + } + return $cond_cats; + } +} +?> diff --git a/gui/baculum/protected/Web/Class/WebBasicUserManager.php b/gui/baculum/protected/Web/Class/WebBasicUserManager.php new file mode 100644 index 000000000..ea3a6db06 --- /dev/null +++ b/gui/baculum/protected/Web/Class/WebBasicUserManager.php @@ -0,0 +1,45 @@ + + * @category Module + * @package Baculum Web + */ +class WebBasicUserManager extends WebModule implements UserManager { + + public function init($config) { + } + + public function validateUser($username, $password) { + /** + * Basic auth is realized by web server, so validating + * user/pass is always true here. + */ + return true; + } +} +?> diff --git a/gui/baculum/protected/Web/Class/WebConfig.php b/gui/baculum/protected/Web/Class/WebConfig.php index 47732498e..3fb1212dd 100644 --- a/gui/baculum/protected/Web/Class/WebConfig.php +++ b/gui/baculum/protected/Web/Class/WebConfig.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -21,6 +21,7 @@ */ Prado::using('Application.Common.Class.ConfigFileModule'); +Prado::using('Application.Common.Class.Crypto'); /** * Manage webGUI configuration. @@ -45,36 +46,87 @@ class WebConfig extends ConfigFileModule { /** * Default application language */ - const DEFAULT_LANGUAGE = 'en'; + const DEF_LANG = 'en'; + + /** + * Default number of jobs visible in tables/ + */ + const DEF_MAX_JOBS = 15000; + + /** + * Default size values unit. + */ + const DEF_SIZE_VAL_UNIT = 'decimal'; + + /** + * Default value for showing time in job log. + */ + const DEF_TIME_IN_JOB_LOG = 0; + + /** + * Supported authentication methods. + */ + const AUTH_METHOD_BASIC = 'basic'; + const AUTH_METHOD_LDAP = 'ldap'; + + /** + * Default access options. + */ + const DEF_ACCESS_NO_ACCESS = 'no_access'; + const DEF_ACCESS_DEFAULT_SETTINGS = 'default_settings'; + + /** + * Stores web config content. + */ + private $config = null; /** * These options are obligatory for web config. */ private $required_options = array( - 'baculum' => array('login', 'debug', 'lang') + 'baculum' => array('debug', 'lang') ); /** - * Get (read) web config. + * Initialize module configuration. + * + * @param TXmlElement $config module configuration + * @return none + */ + public function init($config) { + // add event handler to set page language + $this->Application->attachEventHandler( + 'onPreRunService', + [$this, 'setCulture'] + ); + } + + /** + * Get web config. * * @access public * @param string $section config section name * @return array config */ public function getConfig($section = null) { - $config = $this->readConfig(self::CONFIG_FILE_PATH, self::CONFIG_FILE_FORMAT); - if ($this->validateConfig($config) === true) { - if (!is_null($section)) { - $config = array_key_exists($section, $this->required_options) ? $config[$section] : array(); + $config = []; + $valid = true; + if (is_null($this->config)) { + $this->config = $this->readConfig(self::CONFIG_FILE_PATH, self::CONFIG_FILE_FORMAT); + $valid = $this->validateConfig($this->config); + } + if ($valid === true) { + if (is_string($section)) { + $config = key_exists($section, $this->config) ? $this->config[$section] : array(); + } else { + $config = $this->config; } - } else { - $config = array(); } return $config; } /** - * Set (save) web config. + * Set web config. * * @access public * @param array $config config @@ -84,6 +136,9 @@ class WebConfig extends ConfigFileModule { $result = false; if ($this->validateConfig($config) === true) { $result = $this->writeConfig($config, self::CONFIG_FILE_PATH, self::CONFIG_FILE_FORMAT); + if ($result === true) { + $this->config = null; + } } return $result; } @@ -103,34 +158,171 @@ class WebConfig extends ConfigFileModule { } /** - * Get web administrator name. - * Web interface supports one admin and many normal users. + * Set default options for new config. * - * @return string web admin name + * @param array $opts custom options to add to default options + * @return true on success, otherwise false */ - public function getWebAdmin() { + public function setDefConfigOpts($opts = []) { $config = $this->getConfig(); - $web_admin = $config['baculum']['login']; - return $web_admin; + + $baculum = [ + 'debug' => 0, + 'lang' => self::DEF_LANG, + 'max_jobs' => self::DEF_MAX_JOBS, + 'size_values_unit' => self::DEF_SIZE_VAL_UNIT, + 'time_in_job_log' => self::DEF_TIME_IN_JOB_LOG + ]; + if (key_exists('baculum', $config)) { + $config['baculum'] = array_merge($baculum, $config['baculum']); + } else { + $config['baculum'] = $baculum; + } + + $user_file = $this->getModule('basic_webuser')->getConfigPath(); + // basic options + $auth_basic = [ + 'allow_manage_users' => 1, + 'user_file' => $user_file, + 'hash_alg' => Crypto::HASH_ALG_APR1_MD5 + ]; + if (key_exists('auth_basic', $config)) { + $config['auth_basic'] = array_merge($auth_basic, $config['auth_basic']); + } else { + $config['auth_basic'] = $auth_basic; + } + + // security options + $security = [ + 'auth_method' => self::AUTH_METHOD_BASIC, + 'def_access' => self::DEF_ACCESS_DEFAULT_SETTINGS, + 'def_role' => WebUserRoles::NORMAL, + 'def_api_host' => HostConfig::MAIN_CATALOG_HOST + ]; + if (key_exists('security', $config)) { + $config['security'] = array_merge($security, $config['security']); + } else { + $config['security'] = $security; + } + + if (count($opts) > 0) { + $config = array_replace_recursive($config, $opts); + } + + // set default properties + $ret = $this->setConfig($config); + if ($ret !== true) { + $emsg = 'Error while saving auth basic config.'; + $this->getModule('logging')->log( + __FUNCTION__, + $emsg, + Logging::CATEGORY_APPLICATION, + __FILE__, + __LINE__ + ); + } + return $ret; } /** - * Get application language short name. - * If no language set then returned is default language. + * Get authentication method. * - * @access public - * @return string lanuage short name + * @return string authentication method */ - public function getLanguage() { - $language = null; + public function getAuthMethod() { $config = $this->getConfig(); - if (array_key_exists('baculum', $config) && array_key_exists('lang', $config['baculum'])) { - $language = $config['baculum']['lang']; + + $auth_method = self::AUTH_METHOD_BASIC; // Basic is default method + if (isset($config['security']['auth_method'])) { + $auth_method = $config['security']['auth_method']; } - if (is_null($language)) { - $language = self::DEFAULT_LANGUAGE; + return $auth_method; + } + + /** + * Check if current authentication method is set to Basic. + * + * @return boolean true if is set Basic auth, otherwise false + */ + public function isAuthMethodBasic() { + return ($this->getAuthMethod() === self::AUTH_METHOD_BASIC); + } + + /** + * Check if current authentication method is set to LDAP. + * + * @return boolean true if is set LDAP auth, otherwise false + */ + public function isAuthMethodLdap() { + return ($this->getAuthMethod() === self::AUTH_METHOD_LDAP); + } + + /** + * Check if current default access method for not existing users + * in configuration file is set to no access. + * + * @return boolean true if is set no access, otherwise false + */ + public function isDefAccessNoAccess() { + $config = $this->getConfig(); + return (isset($config['security']['def_access']) && $config['security']['def_access'] === self::DEF_ACCESS_NO_ACCESS); + } + + /** + * Check if current default access method for not existing users + * in configuration file is set to default settings. + * + * @return boolean true if is set default settings, otherwise false + */ + public function isDefAccessDefaultSettings() { + $config = $this->getConfig(); + return (isset($config['security']['def_access']) && $config['security']['def_access'] === self::DEF_ACCESS_DEFAULT_SETTINGS); + } + + /** + * Set culture for whole page. + * Uses currently set language settings. + * + * @return none + */ + public function setCulture() { + $this->Application->getGlobalization()->Culture = $this->getLanguage(); + } + + /** + * Get curently set language short name (for example: en, pl). + * If language short name is not set in session then the language value + * is taken from Baculum config file, saved in session and returned. + * If the language setting is set in session, then the value from + * session is returned. + * + * @return string currently set language short name + */ + public function getLanguage() { + $language = null; + if (isset($_SESSION['language']) && !empty($_SESSION['language'])) { + $language = $_SESSION['language']; + } else { + $config = $this->getConfig(); + if (isset($config['baculum']) && key_exists('lang', $config['baculum'])) { + $language = $config['baculum']['lang']; + } + if (is_null($language)) { + $language = self::DEF_LANG; + } + $_SESSION['language'] = $language; } return $language; } + + /** + * Set language for current page. + * Note, it is done in session only. + * + * @return none + */ + public function setLanguage($lang) { + $_SESSION['language'] = $lang; + } } ?> diff --git a/gui/baculum/protected/Web/Class/WebLdapUserManager.php b/gui/baculum/protected/Web/Class/WebLdapUserManager.php new file mode 100644 index 000000000..3f939de53 --- /dev/null +++ b/gui/baculum/protected/Web/Class/WebLdapUserManager.php @@ -0,0 +1,49 @@ + + * @category Module + * @package Baculum Web + */ +class WebLdapUserManager extends WebModule implements UserManager { + + private $ldap = null; + + public function init($config) { + parent::init($config); + $web_config = $this->getModule('web_config')->getConfig(); + if (key_exists('auth_ldap', $web_config)) { + $this->ldap = $this->getModule('ldap'); + $this->ldap->setParams($web_config['auth_ldap']); + } + } + + public function validateUser($username, $password) { + return $this->ldap->login($username, $password); + } +} +?> diff --git a/gui/baculum/protected/Web/Class/WebRoleConfig.php b/gui/baculum/protected/Web/Class/WebRoleConfig.php new file mode 100644 index 000000000..f6d49d342 --- /dev/null +++ b/gui/baculum/protected/Web/Class/WebRoleConfig.php @@ -0,0 +1,186 @@ + + * @category Module + * @package Baculum Web + */ +class WebRoleConfig extends ConfigFileModule { + + /** + * Web role name allowed characters pattern + */ + const ROLE_PATTERN = '[\w\@\-\.]+'; + + /** + * Web role config file path + */ + const CONFIG_FILE_PATH = 'Application.Web.Config.roles'; + + /** + * Web role config file format + */ + const CONFIG_FILE_FORMAT = 'ini'; + + /** + * These options are obligatory for web config. + */ + private $role_required_options = [ + 'long_name', + 'description', + 'enabled', + 'resources' + ]; + + /** + * Stores web roles config content. + */ + private $config = null; + + /** + * Get (read) web role config. + * + * @return array config + */ + public function getConfig() { + if (is_null($this->config)) { + $this->config = $this->getConfigInternal(); + } + return $this->config; + } + + private function getConfigInternal() { + $roles_config = []; + $config = $this->readConfig(self::CONFIG_FILE_PATH, self::CONFIG_FILE_FORMAT); + // Web role config validation per single role + foreach ($config as $role => $role_config) { + if ($this->isRoleConfigValid([$role => $role_config]) === false) { + /** + * If config for one web role is invalid, don't add this role config + * but continue checking next role config sections. + * It is because during adding new role manually, admin can do a typo + * in new role config section. Validation errors are logged. + */ + continue; + } + $role_config['role'] = $role; + $roles_config[$role] = $role_config; + } + return $roles_config; + } + + /** + * Set web role config. + * Method is private, because this method saves whole web role config. + * To add (or modify) role in config, use method to save single role in config. + * @see setRoleConfig() + * + * @param array $config config + * @return boolean true if config saved successfully, otherwise false + */ + public function setConfig(array $config) { + $result = false; + if ($this->isRoleConfigValid($config) === true) { + $result = $this->writeConfig($config, self::CONFIG_FILE_PATH, self::CONFIG_FILE_FORMAT); + if ($result === true) { + $this->config = null;; + } + } + return $result; + } + + /** + * Get single role config. + * + * @param $role role name + * @return array role config + */ + public function getRoleConfig($role) { + $role_config = []; + $config = $this->getConfig(); + if (key_exists($role, $config)) { + $config[$role]['role'] = $role; + $role_config = $config[$role]; + } + return $role_config; + } + + /** + * Set single role config. + * + * @param string $role role name + * @param array $role config + * @return boolean true if config saved successfully, otherwise false + */ + public function setRoleConfig($role, array $role_config) { + $config = $this->getConfig(); + $config[$role] = $role_config; + return $this->setConfig($config); + } + + /** + * Validate role single role section in config. + * + * @param array $config role config section + * @return boolean true if config valid, otherwise false + */ + private function isRoleConfigValid(array $config) { + $invalid = ['required' => []]; + foreach ($config as $role => $role_config) { + for ($i = 0; $i < count($this->role_required_options); $i++) { + if (!key_exists($this->role_required_options[$i], $role_config)) { + $invalid['required'][] = [ + 'role' => $role, + 'value' => $this->role_required_options[$i], + 'type' => 'option' + ]; + } + } + } + + $valid = true; + $required_len = count($invalid['required']); + if ($required_len > 0) { + $valid = false; + $emsg = ''; + $path = $this->getConfigRealPath(self::CONFIG_FILE_PATH); + for ($i = 0; $i < $required_len; $i++) { + $emsg = "ERROR [$path] Required {$invalid['required'][$i]['type']} '{$invalid['required'][$i]['value']}' not found for role '{$invalid['required'][$i]['role']}."; + $this->Application->getModule('logging')->log( + __FUNCTION__, + $emsg, + Logging::CATEGORY_APPLICATION, + __FILE__, + __LINE__ + ); + } + } + return $valid; + } +} +?> diff --git a/gui/baculum/protected/Web/Class/WebUser.php b/gui/baculum/protected/Web/Class/WebUser.php new file mode 100644 index 000000000..3b9e22bcd --- /dev/null +++ b/gui/baculum/protected/Web/Class/WebUser.php @@ -0,0 +1,251 @@ + + * @category Module + * @package Baculum Web + */ +class WebUser extends TUser { + + /** + * Saved in session user properties. + */ + const LONG_NAME = 'LongName'; + const EMAIL = 'Email'; + const DESCRIPTION = 'Description'; + const API_HOSTS = 'ApiHosts'; + const IPS = 'Ips'; + const ENABLED = 'Enabled'; + const IN_CONFIG = 'InConfig'; + + /** + * Create single user instance. + * Used for authenticated users. + * If user doesn't exist in configuration then default access values can be taken into accout. + * + * @param string username user name + * @return WebUser user instance + */ + public function createUser($username) { + $user = Prado::createComponent(__CLASS__, $this->getManager()); + $user->setUsername($username); + + $application = $this->getManager()->getApplication(); + $user_config = $application->getModule('user_config')->getUserConfig($username); + $web_config = $application->getModule('web_config')->getConfig(); + + if (count($user_config) > 0) { + // User exists in Baculum Web users database + $user->setInConfig(true); + $user->setDescription($user_config['description']); + $user->setLongName($user_config['long_name']); + $user->setEmail($user_config['email']); + $user->setRoles($user_config['roles']); + $user->setAPIHosts($user_config['api_hosts']); + $user->setIps($user_config['ips']); + $user->setEnabled($user_config['enabled']); + } elseif (isset($web_config['security']['def_access'])) { + // User doesn't exist. Check if user can have access. + $user->setInConfig(false); + if ($web_config['security']['def_access'] === WebConfig::DEF_ACCESS_NO_ACCESS) { + // no access, nothing to do + } elseif ($web_config['security']['def_access'] === WebConfig::DEF_ACCESS_DEFAULT_SETTINGS) { + if (isset($web_config['security']['def_role'])) { + $user->setRoles($web_config['security']['def_role']); + } + if (isset($web_config['security']['def_api_host'])) { + $user->setAPIHosts($web_config['security']['def_api_host']); + } + } + } + return $user; + } + + /** + * Username setter. + * + * @param string $username user name + * @return none + */ + public function setUsername($username) { + $this->setName($username); + } + + /** + * Username getter. + * + * @return string user name + */ + public function getUsername() { + return $this->getName(); + } + + /** + * Long name setter. + * + * @param string $long_name long name + * @return none + */ + public function setLongName($long_name) { + $this->setState(self::LONG_NAME, $long_name); + } + + /** + * Long name getter. + * + * @return string long name (default empty string) + */ + public function getLongName() { + return $this->getState(self::LONG_NAME, ''); + } + + /** + * E-mail address setter. + * + * @param string $email e-mail address + * @return none + */ + public function setEmail($email) { + $this->setState(self::EMAIL, $email); + } + + /** + * E-mail address getter. + * + * @return string e-mail address + */ + public function getEmail() { + return $this->getState(self::EMAIL, ''); + } + + /** + * Description setter. + * + * @param string $desc description + * @return none + */ + public function setDescription($desc) { + $this->setState(self::DESCRIPTION, $desc); + } + + /** + * Description getter. + * + * @return string description + */ + public function getDescription() { + return $this->getState(self::DESCRIPTION, ''); + } + + /** + * Set API hosts. + * So far is supported only one API host per user. + * In the future this method can support more API hosts per user. + * + * @param string $api_hosts API hosts + * @return none + */ + public function setAPIHosts($api_hosts) { + $this->setState(self::API_HOSTS, $api_hosts); + } + + /** + * API hosts getter. + * + * @return string API host + */ + public function getAPIHosts() { + $hosts = $this->getState(self::API_HOSTS); + $hosts = explode(',', $hosts); + if (count($hosts) == 1 && !empty($hosts[0])) { + $api_hosts = $hosts[0]; + } else { + // default API host + $api_hosts = HostConfig::MAIN_CATALOG_HOST; + } + return $api_hosts; + } + + /** + * IP address restriction setter. + * + * @param string $ips comma separated IP addresses + * @return none + */ + public function setIps($ips) { + $this->setState(self::IPS, $ips); + } + + /** + * IP address restriction getter. + * + * @return string comma separated IP address list (default empty string) + */ + public function getIps() { + return $this->getState(self::IPS, ''); + } + + /** + * Enabled setter + * + * @param boolean $enabled enabled flag state + * @return none + */ + public function setEnabled($enabled) { + $enabled = TPropertyValue::ensureBoolean($enabled); + $this->setState(self::ENABLED, $enabled); + } + + /** + * Enabled getter. + * + * @return string enabled flag state (default false) + */ + public function getEnabled() { + return $this->getState(self::ENABLED, false); + } + + /** + * Set if user exists in configuration file. + * + * @param boolean $in_config in config state value + * @return none + */ + public function setInConfig($in_config) { + $in_config = TPropertyValue::ensureBoolean($in_config); + $this->setState(self::IN_CONFIG, $in_config); + } + + /** + * In config getter. + * + * @return string in config state value (default false) + */ + public function getInConfig() { + return $this->getState(self::IN_CONFIG, false); + } +} +?> diff --git a/gui/baculum/protected/Web/Class/WebUserConfig.php b/gui/baculum/protected/Web/Class/WebUserConfig.php new file mode 100644 index 000000000..256a8b04d --- /dev/null +++ b/gui/baculum/protected/Web/Class/WebUserConfig.php @@ -0,0 +1,289 @@ + + * @category Module + * @package Baculum Web + */ +class WebUserConfig extends ConfigFileModule { + + /** + * Web user name allowed characters pattern + */ + const USER_PATTERN = '[\w\@\-\.]+'; + + /** + * Regular expression to validate e-mail address. + * Note, there exists TEmailAddressValidator control but it doesn't allow + * local e-mail address, for example root@localhost or gani@darkstar. + * + * @see http://www.regular-expressions.info/email.html + */ + const EMAIL_ADDRESS_PATTERN = '[a-zA-Z0-9!#$%&\'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&\'*+/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.?)+'; + + /** + * Web user config file path + */ + const CONFIG_FILE_PATH = 'Application.Web.Config.users'; + + /** + * Web user config file format + */ + const CONFIG_FILE_FORMAT = 'ini'; + + /** + * Stores web user config content. + */ + private $config = null; + + /** + * These properties are obligatory for web config. + */ + private $user_req_prop = [ + 'long_name', + 'description', + 'email', + 'roles', + 'api_hosts', + 'enabled', + 'ips' + ]; + + /** + * Get web user config. + * + * @return array config + */ + public function getConfig() { + $config = []; + if (is_null($this->config)) { + $this->config = $this->readConfig(self::CONFIG_FILE_PATH, self::CONFIG_FILE_FORMAT); + } + // Web user config validation per single user + foreach ($this->config as $username => $user_config) { + if ($this->isUserConfigValid([$username => $user_config]) === false) { + /** + * If config for one web user is invalid, don't add this user config + * but continue checking next user config sections. + * It is because during adding new user manually, admin can do a typo + * in new user config section. Validation errors are logged. + */ + continue; + } + $user_config['username'] = $username; + $config[$username] = $user_config; + } + return $config; + } + + /** + * Set web user config. + * Method is private, because this method saves whole web user config. + * To add (or modify) user in config, use method to save single user in config. + * @see setUserConfig() + * + * @param array $config config + * @return boolean true if config saved successfully, otherwise false + */ + public function setConfig(array $config) { + $result = false; + if ($this->isUserConfigValid($config) === true) { + $result = $this->writeConfig($config, self::CONFIG_FILE_PATH, self::CONFIG_FILE_FORMAT); + if ($result === true) { + $this->config = null; + } + } + return $result; + } + + public function configExists() { + return parent::isConfig(self::CONFIG_FILE_PATH); + } + + /** + * Get single user config. + * + * @param $user user name + * @return array user config + */ + public function getUserConfig($username) { + $user_config = []; + $config = $this->getConfig(); + if (key_exists($username, $config)) { + $config[$username]['username'] = $username; + $user_config = $config[$username]; + } + return $user_config; + } + + /** + * Set single user config. + * + * @param string $username user name + * @param array $user_config user configuration + * @return boolean true if config saved successfully, otherwise false + */ + public function setUserConfig($username, array $user_config) { + $config = $this->getConfig(); + $config[$username] = $user_config; + if (key_exists('username', $user_config)) { + unset($user_config['username']); + } + return $this->setConfig($config); + } + + /** + * Get user config properties. + * If custom properties provided, they are merged with required properties. + * + * @param array $prop custom user properties + * @return array user config properties + */ + public function getUserConfigProps($prop = []) { + $req_prop = array_fill_keys($this->user_req_prop, ''); + return array_merge($req_prop, $prop); + } + + + /** + * Import basic auth users to web user config. + * It can be done both if web user config hasn't been created yet + * and if web user config exists already with some users. + * + * @return boolean true if config with imported users saved successfully, + * otherwise false + */ + public function importUsersToConfig() { + $basic_users = $this->getModule('basic_webuser')->getUsers(); + $web_config = $this->getModule('web_config')->getConfig(); + $users = array_keys($basic_users); + sort($users); + $users_list = []; + $is_users_config = (key_exists('users', $web_config) && is_array($web_config['users'])); + $user_count = count($users); + for ($i = 0; $i < $user_count; $i++) { + $host = ''; + if ($is_users_config && key_exists($users[$i], $web_config['users'])) { + $host = $web_config['users'][$users[$i]]; + } + $role = ''; + if ((isset($web_config['baculum']['login']) && $users[$i] === $web_config['baculum']['login']) || $user_count === 1) { + $role = WebUserRoles::ADMIN; + } else { + $role = WebUserRoles::NORMAL; + } + $users_list[$users[$i]] = $this->getUserConfigProps([ + 'roles' => $role, + 'api_hosts' => $host, + 'enabled' => 1 + ]); + } + /** + * Merge existing user configs if any. + * Already existing users overwrites basic user configs if the same users + * exists in basic user config and web user configuration. + */ + $users_list = array_merge($users_list, $this->getConfig()); + return $this->setConfig($users_list); + } + + /** + * Import users config from basic auth file into users.conf file. + * If users config file doesn't exist, create it and populate + * using basic users file. It is one time operation. + * Basic auth method is the main Baculum Web auth method. Before introducing + * users.conf file, it was the only one supported method. + * + * @return boolean true on successfull import, otherwise false + */ + public function importUsers() { + // import can take place only if user config file doesn't exist + if (parent::isConfig(self::CONFIG_FILE_PATH) === true) { + return false; + } + + // import users + $ret = $this->importUsersToConfig(); + if ($ret === true) { + $ret = $this->getModule('web_config')->setDefConfigOpts(); + } else { + $ret = false; + $emsg = 'Error while importing basic users.'; + $this->getModule('logging')->log( + __FUNCTION__, + $emsg, + Logging::CATEGORY_APPLICATION, + __FILE__, + __LINE__ + ); + } + return $ret; + } + + /** + * Validate user single user section in config. + * + * @param array $config user config section + * @return boolean true if config valid, otherwise false + */ + private function isUserConfigValid(array $config) { + $invalid = ['required' => []]; + foreach ($config as $username => $user_config) { + for ($i = 0; $i < count($this->user_req_prop); $i++) { + if (!key_exists($this->user_req_prop[$i], $user_config)) { + $invalid['required'][] = [ + 'username' => $username, + 'value' => $this->user_req_prop[$i], + 'type' => 'option' + ]; + } + } + } + + $valid = true; + $required_len = count($invalid['required']); + if ($required_len > 0) { + $valid = false; + $emsg = ''; + $path = $this->getConfigRealPath(self::CONFIG_FILE_PATH); + for ($i = 0; $i < $required_len; $i++) { + $emsg = "ERROR [$path] Required {$invalid['required'][$i]['type']} '{$invalid['required'][$i]['value']}' not found for user '{$invalid['required'][$i]['username']}."; + $this->Application->getModule('logging')->log( + __FUNCTION__, + $emsg, + Logging::CATEGORY_APPLICATION, + __FILE__, + __LINE__ + ); + } + } + return $valid; + } +} +?> diff --git a/gui/baculum/protected/Web/Class/WebUserManager.php b/gui/baculum/protected/Web/Class/WebUserManager.php new file mode 100644 index 000000000..dc2416b1c --- /dev/null +++ b/gui/baculum/protected/Web/Class/WebUserManager.php @@ -0,0 +1,407 @@ + + * @category Module + * @package Baculum Web + */ +class WebUserManager extends WebModule implements IUserManager { + + /** + * Authorized flag key. + * This flag is used to set authorization state. + */ + const SET_AUTHROIZED_FLAG = 'AuthorizedFlag'; + + /** + * User class to represent single user instance + */ + private $user_class = ''; + + /** + * Guest name. + */ + private $guest_name = 'guest'; + + /** + * Stores object used to create users (factory) + */ + private $user_factory; + + /** + * Initialize module configuration. + * + * @param TXmlElement $config module configuration + * @return none + */ + public function init($config) { + $this->user_factory = Prado::createComponent($this->user_class, $this); + $this->setAuthorizedFlag(null); + $this->getModule('auth')->attachEventHandler( + 'OnAuthenticate', + [$this, 'doAuthentication'] + ); + $this->Application->attachEventHandler( + 'onAuthorization', + [$this, 'doAuthorization'] + ); + $this->Application->attachEventHandler( + 'onAuthorizationComplete', + [$this, 'doAuthorizationComplete'] + ); + } + + /** + * Create and get new user object. + * + * @param mixed $username username string or null for guests + */ + public function getUser($username = null) { + $user = null; + if (is_null($username)) { + $user = Prado::createComponent($this->user_class, $this); + $user->setIsGuest(true); + } else { + $user = $this->user_factory->createUser($username); + $user->setIsGuest(false); + } + return $user; + } + + /** + * Used for authentication. + * It does login try. + * + * @param string $username username + * @param string $password password + * @return boolean true if user and password are valid, false otherwise + */ + public function validateUser($username, $password) { + $valid = false; + $manager_cls = $this->getUserManagerClass(); + if (!empty($manager_cls)) { + $manager = Prado::createComponent($manager_cls); + $manager->init(null); + $valid = $manager->validateUser($username, $password); + } + return $valid; + } + + /** + * User class getter. + * + * @return string user class name + */ + public function getUserClass() { + return $this->user_class; + } + + /** + * User class setter. + * + * @param string $cls user class name + * @return none + */ + public function setUserClass($cls) { + $this->user_class = $cls; + } + + /** + * Guest name getter. + * + * @return guest name + */ + public function getGuestName() { + return $this->guest_name; + } + + /** + * Guest name setter. + * + * @param string guest name + * @return none + */ + public function setGuestName($name) { + $this->guest_name = $name; + } + + /** + * Used for getting stored in cookie information about user. + * Useful to keep login between sessions. + * + * @param THttpCookie $cookie cookie object + * @return none + */ + public function getUserFromCookie($cookie) { + // not implemented + } + + /** + * Used for setting in cookie information about user. + * Useful to keep login between sessions. + * + * @param THttpCookie $cookie cookie object + * @return none + */ + public function saveUserToCookie($cookie) { + // not implemented + } + + /** + * Check if currently loading page is allowed for current user. + * + * @param WebUser $user user object + * @param string $page_path page path + */ + public function isPageAllowed($user, $page_path) { + $allowed = false; + $page_roles = $this->getModule('user_role')->getRolesByPagePath($page_path); + $user_roles = $user->getRoles(); + for ($i = 0; $i < count($user_roles); $i++) { + if (in_array($user_roles[$i], $page_roles)) { + $allowed = true; + break; + } + } + return $allowed; + } + + /** + * Get user manager class. + * It is switcher between different authentication backends. + * + * @return string user manager class path + */ + private function getUserManagerClass() { + $cls = null; + $auth_method = $this->getModule('web_config')->getAuthMethod(); + + switch ($auth_method) { + case WebConfig::AUTH_METHOD_BASIC: + $cls = 'Application.Web.Class.WebBasicUserManager'; + break; + case WebConfig::AUTH_METHOD_LDAP: + $cls = 'Application.Web.Class.WebLdapUserManager'; + break; + default: + $cls = 'Application.Web.Class.WebBasicUserManager'; + } + return $cls; + } + + /** + * Event handler attached to the application authentication event. + * It does auto-login for Basic authentication users and applies + * authorization rules before they are used in authorization phase. + * + * @param TApplication $application application object + * @return none + */ + public function doAuthentication($application) { + if ($this->getModule('web_config')->isAuthMethodBasic() && $application->getUser()->IsGuest) { + /** + * For basic method the authentication is realized by web server. + * From this reason do log in autmatically. + */ + // For basic auth take username from web server. + if (key_exists('PHP_AUTH_USER' , $_SERVER) && !empty($_SERVER['PHP_AUTH_USER'])) { + $username = $_SERVER['PHP_AUTH_USER']; + $password = ''; + $this->getModule('auth')->login($username, $password); + $this->Response->redirect('/'); + } else { + $emsg = 'Basic auth is enabled, but PHP_AUTH_USER is not set or is empty.'; + $this->Application->getModule('logging')->log( + __FUNCTION__, + $emsg, + Logging::CATEGORY_SECURITY, + __FILE__, + __LINE__ + ); + } + } + + $this->applyAuthorizationRules($application); + } + + /** + * Apply authorization rules. + * It is important place because here are defined authorization rules for currently + * logged in user and currently visited page. + * + * @param TApplication $application application object + */ + private function applyAuthorizationRules($application) { + $page_path = $this->getService()->getRequestedPagePath(); + $page_roles = $this->getModule('user_role')->getRolesByPagePath($page_path, false); + $auth_rules = $this->getApplication()->getAuthorizationRules(); + $web_config = $this->getModule('web_config'); + $roles = implode(',', $page_roles); + $users = ''; + $ips = $application->User->getIps(); + $pc = $this->getModule('page_category'); + $add_role = false; + if ($pc->isCategorySystem($page_path) || $pc->isCategoryConditional($page_path)) { + // allow system pages for all logged in users + $roles = ''; + $users = '@'; + $add_role = true; + } + + if ($pc->isCategoryPublic($page_path)) { + // public pages are available for everybody + $roles = $ips = ''; + $users = '*'; + $add_role = true; + } + + // remove authorization rules if any + $auth_rules->clear(); + + /** + * Rules for current page. + * NOTE: items order has meaning. + */ + $allow_rule = [ + 'action' => 'allow', + 'roles' => $roles, + 'users' => $users, + 'verb' => '', + 'ips' => $ips + ]; + + $deny_rule = [ + 'action' => 'deny', + 'roles' => '', + 'users' => '*', + 'verb' => '', + 'ips' => '' + ]; + $rules = []; + + /** + * Add allow rule for enabled users, for special pages (system and public) + * and if user doesn't exist in user config and access by default setting + * is enabled. + */ + if ($application->User->Enabled === true || $add_role === true || (!$application->User->InConfig && $web_config->isDefAccessDefaultSettings())) { + // Add allow rules for user with set enabled flag + $rules[] = $allow_rule; + } + // Deny everything else + $rules[] = $deny_rule; + + // Add authorization rules + for ($i = 0; $i < count($rules); $i++) { + $rule = new TAuthorizationRule( + $rules[$i]['action'], + $rules[$i]['users'], + $rules[$i]['roles'], + $rules[$i]['verb'], + $rules[$i]['ips'] + ); + $auth_rules->insertAt($i, $rule); + } + } + + /** + * Set authorization flag. + * This flag is to know if user finished authorization process successfully or not. + * The flag is set in temporary on authorization process and it is set to null + * just after the authorization finishes with success. If authorization fails + * then onAuthorizationComplete event isn't fired because all process page loading + * is stopped on onAuthorization event. + * NOTE: This flag has only informational character. All authorization work is + * done by the framework. + * + * @param string|null $state flag state + * @return none + */ + public function setAuthorizedFlag($state) { + $this->Application->getSession()->add(self::SET_AUTHROIZED_FLAG, $state); + } + + /** + * Get authorization flag. + * + * @return string|null flag state + */ + public function getAuthorizedFlag() { + return $this->Application->getSession()->itemAt(self::SET_AUTHROIZED_FLAG); + } + + /** + * Event handler attached to the application authorization event. + * It sets the authorization flag. + * + * @param TApplication $param application object + */ + public function doAuthorization($application) { + $service = $this->Application->getService(); + $auth = $this->getModule('auth'); + $page = $service->getRequestedPagePath(); + if (($service instanceof TPageService) && $page !== $auth->getLoginPage()) { + $this->setAuthorizedFlag($page); + } + } + + /** + * Event handler attached to the application authorization event. + * It sets the authorization flag. + * + * @param TApplication $param application object + */ + public function doAuthorizationComplete($application) { + $service = $this->Application->getService(); + $auth = $this->getModule('auth'); + $page = $service->getRequestedPagePath(); + if (($service instanceof TPageService) && $page === $this->getAuthorizedFlag()) { + $this->setAuthorizedFlag(null); + } + } + + /** + * Returns authorization state for current page request. + * Because the framework doesn't provide any public method to check + * whether authorization finished successfully, this method can + * be used for that purpose. + * NOTE: Use it after onAuthorizationComplete application event, + * not before. + * + * @return boolean true if authorization finished successfully, otherwise false + */ + public function isAuthorized() { + $web_config = $this->getModule('web_config'); + $user = $this->Application->getUser(); + return ($this->getAuthorizedFlag() === null && + (($user->InConfig && !$web_config->isDefAccessNoAccess()) || + (!$user->InConfig && $web_config->isDefAccessDefaultSettings())) + ); + } +} +?> diff --git a/gui/baculum/protected/Web/Class/WebUserRoles.php b/gui/baculum/protected/Web/Class/WebUserRoles.php new file mode 100644 index 000000000..7af51e0ec --- /dev/null +++ b/gui/baculum/protected/Web/Class/WebUserRoles.php @@ -0,0 +1,160 @@ + + * @category Module + * @package Baculum Web + */ +class WebUserRoles extends WebModule { + + /** + * Pre-defined roles. + */ + const ADMIN = 'admin'; + const NORMAL = 'normal'; + + /** + * Single role properties. + */ + private $role_prop = [ + 'role', + 'long_name', + 'description', + 'enabled', + 'resources' + ]; + + /** + * Get pre-defined roles with available resources for them. + * + * @return array pre-defined roles + */ + public function getPreDefinedRoles() { + $roles = []; + + // Admin user resources + $res = $this->getModule('page_category')->getCategories(false); + $admin_res = array_values($res); + $roles[self::ADMIN] = array_combine($this->role_prop, [ + self::ADMIN, + 'Administrator', + 'Role with full access', + '1', + implode(',', $admin_res) + ]); + + // Normal user resources + $res = [ + PageCategory::DASHBOARD, + PageCategory::JOB_HISTORY_LIST, + PageCategory::JOB_HISTORY_VIEW, + PageCategory::JOB_LIST, + PageCategory::JOB_VIEW, + PageCategory::CLIENT_LIST, + PageCategory::CLIENT_VIEW, + PageCategory::RESTORE_WIZARD, + PageCategory::GRAPHS + ]; + $roles[self::NORMAL] = array_combine($this->role_prop, [ + self::NORMAL, + 'Normal user', + 'Role with limitted access', + '1', + implode(',', $res) + ]); + return $roles; + } + + /** + * Check if a role is predefined. + * + * @param string $role nazwa roli + * @return boolean true if role is predefined, otherwise false + */ + public function isRolePreDefined($role) { + $roles = $this->getPreDefinedRoles(); + return key_exists($role, $roles); + } + + /** + * Get all roles pre-defined and defined in config. + * Pre-defined are merged together with defined. + * + * @return array all available roles + */ + public function getRoles() { + $roles = $this->getModule('role_config')->getConfig(); + return array_merge($this->getPreDefinedRoles(), $roles); + } + + /** + * Get single role config. + * + * @param string $role role name + * @return array role config or empty array if role doesn't exist + */ + public function getRole($role) { + $ret = []; + $roles = $this->getRoles(); + if (key_exists($role, $roles)) { + $roles[$role]['role'] = $role; + $ret = $roles[$role]; + } + return $ret; + } + + /** + * Get all roles by specific page path. + * They are roles that have page defined in allowed resources. + * + * @param string $page_path page path + * @return array roles defined for a page + */ + public function getRolesByPagePath($page_path, $with_disabled = true) { + $roles = []; + $all_roles = $this->getRoles(); + foreach ($all_roles as $role => $prop) { + $rs = explode(',', $prop['resources']); + $enabled = (boolean)$prop['enabled']; + if (($enabled || $with_disabled) && in_array($page_path, $rs)) { + $roles[] = $role; + } + } + return $roles; + } + + /** + * Get all pages for specific role. + */ + public function getPagesByRole($role) { + $pages = []; + $role_cfg = $this->getRole($role); + return explode(',', $role_cfg['resources']); + } +} +?> diff --git a/gui/baculum/protected/Web/Init.php b/gui/baculum/protected/Web/Init.php index aa55b8e90..b69fd558d 100644 --- a/gui/baculum/protected/Web/Init.php +++ b/gui/baculum/protected/Web/Init.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -33,6 +33,13 @@ if (!ini_get('date.timezone')) { date_default_timezone_set($timezone); } +/** + * Set time limit to 60 seconds. + * Please note that async requests default times out after 30 seconds + * if not set other value. + */ +set_time_limit(60); + /* * Support for web servers (for example Lighttpd) which do not provide direct * info about HTTP Basic auth to PHP superglobal $_SERVER array. diff --git a/gui/baculum/protected/Web/JavaScript/misc.js b/gui/baculum/protected/Web/JavaScript/misc.js index 5c5342efe..b89b018fa 100644 --- a/gui/baculum/protected/Web/JavaScript/misc.js +++ b/gui/baculum/protected/Web/JavaScript/misc.js @@ -630,132 +630,6 @@ var Dashboard = { } } -var Users = { - ids: { - create_user: { - add_user: 'add_user', - add_user_btn: 'add_user_btn', - newuser: 'newuser', - newpwd: 'newpwd' - }, - change_pwd: { - rel_chpwd: 'chpwd', - rel_chpwd_btn: 'chpwd_btn' - }, - set_host: { - rel_user_host: 'user_host_img' - } - }, - validators: { - user_pattern: null - }, - current_action: null, - init: function() { - this.setEvents(); - }, - setEvents: function() { - document.getElementById(this.ids.create_user.add_user_btn).addEventListener('click', function(e) { - $('#' + this.ids.create_user.add_user).show(); - $('#' + this.ids.create_user.newuser).focus(); - }.bind(this)); - document.getElementById(this.ids.create_user.newuser).addEventListener('keydown', function(e) { - var target = e.target || e.srcElement; - if (e.keyCode == 13) { - this.addUser(); - } else if (e.keyCode == 27) { - this.cancelAddUser(); - } - return false; - }.bind(this)); - document.getElementById(this.ids.create_user.newpwd).addEventListener('keydown', function(e) { - var target = e.target || e.srcElement; - if (e.keyCode == 13) { - this.addUser(); - } else if (e.keyCode == 27) { - this.cancelAddUser(); - } - return false; - }.bind(this)); - }, - userValidator: function(user) { - user = user.replace(/\s/g, ''); - if (user == '') { - alert(this.txt.enter_login); - return false; - } - var valid = this.validators.user_pattern.test(user); - if (valid === false) { - alert(this.txt.invalid_login); - return false; - } - return true; - }, - pwdValidator: function(pwd) { - var valid = pwd.length > 4; - if (valid === false) { - alert(this.txt.invalid_pwd); - } - return valid; - }, - addUser: function() { - var user = document.getElementById(this.ids.create_user.newuser); - var pwd = document.getElementById(this.ids.create_user.newpwd); - if (this.userValidator(user.value) === false) { - return false; - } - if (this.pwdValidator(pwd.value) === false) { - return false; - } - - $('#' + this.ids.create_user.add_user).hide(); - this.action_callback('newuser', user.value, pwd.value); - user.value = ''; - pwd.value = ''; - return true; - }, - rmUser: function(user) { - this.action_callback('rmuser', user); - }, - showChangePwd: function(el) { - $('a[rel=\'' + this.ids.change_pwd.rel_chpwd_btn + '\']').show(); - $('#' + el).hide(); - $('span[rel=\'' + this.ids.change_pwd.rel_chpwd + '\']').hide(); - $(el.nextElementSibling).show(); - $(el.nextElementSibling).find('input')[0].focus(); - }, - changePwd: function(el, user) { - var pwd = el.value; - - if (this.pwdValidator(pwd) === false) { - return false; - } - - $(el.parentNode).hide(); - $(el.parentNode.previousElementSibling).show(); - this.action_callback('chpwd', user, pwd); - return true; - }, - set_host: function(user, select) { - select.nextElementSibling.style.visibility = 'visible'; - this.action_callback('set_host', user, select.value); - }, - hide_loader: function() { - setTimeout(function() { - if (this.current_action === 'set_host') { - $('I[rel=\'' + this.ids.set_host.rel_user_host + '\']').css({visibility: 'hidden'}); - } - }.bind(this), 300); - - }, - cancelAddUser: function() { - $('#' + this.ids.create_user.add_user).hide(); - }, - cancelChangePwd: function(el) { - $(el.parentNode.parentNode).hide(); - } - -}; - var W3SideBar = { ids: { sidebar: 'sidebar', @@ -1122,9 +996,26 @@ dtEscapeRegex = function(value) { return $.fn.dataTable.util.escapeRegex(value); }; +/** + * Do validation comma separated list basing on regular expression + * for particular values. + */ +function validate_comma_separated_list(value, regex) { + var valid = true; + var vals = value.split(','); + var val; + for (var i = 0; i < vals.length; i++) { + val = vals[i].trim(); + if (!val || (regex && !regex.test(val))) { + valid = false; + break; + } + } + return valid; +} function get_table_toolbar(table, actions, txt) { - var table_toolbar = document.querySelector('div.table_toolbar'); + var table_toolbar = document.querySelector('#' + table.table().node().id + '_wrapper div.table_toolbar'); table_toolbar.className += ' dt-buttons'; table_toolbar.style.display = 'none'; var title = document.createTextNode(txt.actions); @@ -1141,6 +1032,9 @@ function get_table_toolbar(table, actions, txt) { select.appendChild(option); acts[actions[i].action] = actions[i]; } + if (actions.length == 1) { + select.selectedIndex = 1; + } var btn = document.createElement('BUTTON'); btn.type = 'button'; btn.className = 'dt-button'; diff --git a/gui/baculum/protected/Web/Lang/en/messages.mo b/gui/baculum/protected/Web/Lang/en/messages.mo index 4fbd8d2ee..12a4ad79e 100644 Binary files a/gui/baculum/protected/Web/Lang/en/messages.mo and b/gui/baculum/protected/Web/Lang/en/messages.mo differ diff --git a/gui/baculum/protected/Web/Lang/en/messages.po b/gui/baculum/protected/Web/Lang/en/messages.po index e00e5187d..af7471b1b 100644 --- a/gui/baculum/protected/Web/Lang/en/messages.po +++ b/gui/baculum/protected/Web/Lang/en/messages.po @@ -1208,12 +1208,6 @@ msgstr "Supported" msgid "Not supported" msgstr "Not supported" -msgid "Authorization to Baculum API" -msgstr "Authorization to Baculum API" - -msgid "Authorization to Baculum Web" -msgstr "Authorization to Baculum Web" - msgid "Step 3 - authentication params to Baculum Web panel" msgstr "Step 3 - authentication params to Baculum Web panel" @@ -1445,9 +1439,6 @@ msgstr "Volume" msgid "During restore there will be used following volumes:" msgstr "During restore there will be used following volumes:" -msgid "Use Ctrl + Mouse Click to change selection" -msgstr "Use Ctrl + Mouse Click to change selection" - msgid "Go back" msgstr "Go back" @@ -2629,3 +2620,306 @@ msgstr "Tip: Use left-click to select table row. Use CTRL + left-click to multip msgid "Show time in job log:" msgstr "Show time in job log:" + +msgid "Security" +msgstr "Security" + +msgid "Roles" +msgstr "Roles" + +msgid "Long name" +msgstr "Long name" + +msgid "Edit user" +msgstr "Edit user" + +msgid "Long name:" +msgstr "Long name:" + +msgid "Description:" +msgstr "Description:" + +msgid "E-mail:" +msgstr "E-mail:" + +msgid "Roles:" +msgstr "Roles:" + +msgid "Invalid e-mail address value." +msgstr "Invalid e-mail address value." + +msgid "Invalid username value." +msgstr "Invalid username value." + +msgid "Username" +msgstr "Username" + +msgid "E-mail" +msgstr "E-mail" + +msgid "Description" +msgstr "Description" + +msgid "Add user" +msgstr "Add user" + +msgid "Username field cannot be empty." +msgstr "Username field cannot be empty." + +msgid "At least one role must be selected." +msgstr "At least one role must be selected." + +msgid "Username with the given name already exists." +msgstr "Username with the given name already exists." + +msgid "Edit role" +msgstr "Edit role" + +msgid "Role with limitted access" +msgstr "Role with limitted access" + +msgid "Role with full access" +msgstr "Role with full access" + +msgid "Role:" +msgstr "Role:" + +msgid "Resources:" +msgstr "Resources:" + +msgid "Role name" +msgstr "Role name" + +msgid "Resources" +msgstr "Resources" + +msgid "Add new role" +msgstr "Add new role" + +msgid "Role field cannot be empty." +msgstr "Role field cannot be empty." + +msgid "At least one resource must be selected." +msgstr "At least one resource must be selected." + +msgid "Role with the given name already exists." +msgstr "Role with the given name already exists." + +msgid "This is native predefined role, that cannot be changed. For having custom roles please use the button to add new role." +msgstr "This is native predefined role, that cannot be changed. For having custom roles please use the button to add new role." + +msgid "The following roles are predefined and cannot be removed: %predefined_roles" +msgstr "The following roles are predefined and cannot be removed: %predefined_roles" + +msgid "The following roles are using by users and cannot be removed: %used_roles To remove them, please unassign all users from these roles." +msgstr "The following roles are using by users and cannot be removed: %used_roles To remove them, please unassign all users from these roles." + +msgid "Add role" +msgstr "Add role" + +msgid "Authentication method" +msgstr "Authentication method" + +msgid "HTTP Basic authentication" +msgstr "HTTP Basic authentication" + +msgid "LDAP authentication" +msgstr "LDAP authentication" + +msgid "LDAP server options" +msgstr "LDAP server options" + +msgid "SSL encryption (LDAPS):" +msgstr "SSL encryption (LDAPS):" + +msgid "Protocol version:" +msgstr "Protocol version:" + +msgid "LDAP version" +msgstr "LDAP version" + +msgid "LDAP authentication method" +msgstr "LDAP authentication method" + +msgid "Anonymous authentication" +msgstr "Anonymous authentication" + +msgid "Simple authentication" +msgstr "Simple authentication" + +msgid "Base DN:" +msgstr "Base DN:" + +msgid "Attributes" +msgstr "Attributes" + +msgid "Please enter port." +msgstr "Please enter port." + +msgid "Please enter username." +msgstr "Please enter username." + +msgid "Manager DN:" +msgstr "Manager DN:" + +msgid "Please enter IP Address/Hostname." +msgstr "Please enter IP Address/Hostname." + +msgid "Please enter username attribute." +msgstr "Please enter username attribute." + +msgid "Test LDAP connection" +msgstr "Test LDAP connection" + +msgid "Manage LDAP users" +msgstr "Manage LDAP users" + +msgid "Please enter Base DN." +msgstr "Please enter Base DN." + +msgid "LDAP user list" +msgstr "LDAP user list" + +msgid "Import users" +msgstr "Import users" + +msgid "Import all users" +msgstr "Import all users" + +msgid "Import selected users" +msgstr "Import selected users" + +msgid "Import users whose fulfill criteria" +msgstr "Import users whose fulfill criteria" + +msgid "Import options:" +msgstr "Import options:" + +msgid "Criteria filter:" +msgstr "Criteria filter:" + +msgid "contains:" +msgstr "contains:" + +msgid "Reload" +msgstr "Reload" + +msgid "Please select users in table to import." +msgstr "Please select users in table to import." + +msgid "Do not overwrite existing Baculum Web users, if they have the same username" +msgstr "Do not overwrite existing Baculum Web users, if they have the same username" + +msgid "Default role for imported users:" +msgstr "Default role for imported users:" + +msgid "Advanced options" +msgstr "Advanced options" + +msgid "Default API host for imported users:" +msgstr "Default API host for imported users:" + +msgid "Log in" +msgstr "Log in" + +msgid "Invalid username or password" +msgstr "Invalid username or password" + +msgid "IP address restrictions" +msgstr "IP address restrictions" + +msgid "IP address restrictions:" +msgstr "IP address restrictions:" + +msgid "Use CTRL + left-click to multiple item selection" +msgstr "Use CTRL + left-click to multiple item selection" + +msgid "Comma separated IP addresses. Using asterisk character, there is also possible to provide subnet, for example: 192.168.1.*" +msgstr "Comma separated IP addresses. Using asterisk character, there is also possible to provide subnet, for example: 192.168.1.*" + +msgid "Default IP address restrictions for imported users:" +msgstr "Default IP address restrictions for imported users:" + +msgid "Set your IP address" +msgstr "Set your IP address" + +msgid "Invalid IP address restrictions value. This field can have comma separated IP addresses only or subnet addresses like 192.168.1.*" +msgstr "Invalid IP address restrictions value. This field can have comma separated IP addresses only or subnet addresses like 192.168.1.*" + +msgid "This type of authentication is realized by web server. To use it, the basic authentication has to be enabled in the Baculum Web web server configuration." +msgstr "This type of authentication is realized by web server. To use it, the basic authentication has to be enabled in the Baculum Web web server configuration." + +msgid "General settings" +msgstr "General settings" + +msgid "Default access setting for logged in users not defined in Baculum Web:" +msgstr "Default access setting for logged in users not defined in Baculum Web:" + +msgid "No access" +msgstr "No access" + +msgid "Access with default settings" +msgstr "Access with default settings" + +msgid "Default role:" +msgstr "Default role:" + +msgid "Default API host:" +msgstr "Default API host:" + +msgid "Users file path:" +msgstr "Users file path:" + +msgid "Hash algorithm:" +msgstr "Hash algorithm:" + +msgid "Please enter users file path value." +msgstr "Please enter users file path value." + +msgid "Test" +msgstr "Test" + +msgid "The user file is not accessible." +msgstr "The user file is not accessible." + +msgid "The user file is not readable by web server user." +msgstr "The user file is not readable by web server user." + +msgid "The user file is readable but not writeable by web server user." +msgstr "The user file is readable but not writeable by web server user." + +msgid "Allow Baculum Web to manage the Basic authentication users (add/remove users and change their passwords)" +msgstr "Allow Baculum Web to manage the Basic authentication users (add/remove users and change their passwords)" + +msgid "Authorization failed. Please contact the Baculum administrator to grant permissions." +msgstr "Authorization failed. Please contact the Baculum administrator to grant permissions." + +msgid "Try again" +msgstr "Try again" + +msgid "Manage Basic users" +msgstr "Manage Basic users" + +msgid "Empty user list" +msgstr "Empty user list" + +msgid "Basic user list" +msgstr "Basic user list" + +msgid "Access to Baculum API" +msgstr "Access to Baculum API" + +msgid "Access to Baculum Web" +msgstr "Access to Baculum Web" + +msgid "Requried support from web server side - e.g. %supported_web_server_list" +msgstr "Requried support from web server side - e.g. %supported_web_server_list" + +msgid "Apache 2.4 with apr-util 1.5+" +msgstr "Apache 2.4 with apr-util 1.5+" + +msgid "Apache, Lighttpd and Nginx on most UNIX platforms" +msgstr "Apache, Lighttpd and Nginx on most UNIX platforms" + +msgid "Nginx on most UNIX platforms" +msgstr "Nginx on most UNIX platforms" diff --git a/gui/baculum/protected/Web/Lang/ja/messages.mo b/gui/baculum/protected/Web/Lang/ja/messages.mo index 67dbea083..cb0b4a519 100644 Binary files a/gui/baculum/protected/Web/Lang/ja/messages.mo and b/gui/baculum/protected/Web/Lang/ja/messages.mo differ diff --git a/gui/baculum/protected/Web/Lang/ja/messages.po b/gui/baculum/protected/Web/Lang/ja/messages.po index 7445d0874..844d641d8 100644 --- a/gui/baculum/protected/Web/Lang/ja/messages.po +++ b/gui/baculum/protected/Web/Lang/ja/messages.po @@ -164,12 +164,6 @@ msgstr "Auth Type:" msgid "Authentication" msgstr "認証" -msgid "Authorization to Baculum API" -msgstr "Baculum API 設定" - -msgid "Authorization to Baculum Web" -msgstr "Baculum Web 管理ユーザー設定" - msgid "Authorization to Baculum panel" msgstr "Authorization to Baculum panel" @@ -1638,9 +1632,6 @@ msgstr "Update slots without barcodes" msgid "Update volumes" msgstr "Update volumes" -msgid "Use Ctrl + Mouse Click to change selection" -msgstr "Use Ctrl + Mouse Click to change selection" - msgid "Use HTTP Basic authentication" msgstr "ベーシック認証使用" @@ -2715,3 +2706,306 @@ msgstr "Tip: Use left-click to select table row. Use CTRL + left-click to multip msgid "Show time in job log:" msgstr "Show time in job log:" + +msgid "Security" +msgstr "Security" + +msgid "Roles" +msgstr "Roles" + +msgid "Long name" +msgstr "Long name" + +msgid "Edit user" +msgstr "Edit user" + +msgid "Long name:" +msgstr "Long name:" + +msgid "Description:" +msgstr "Description:" + +msgid "E-mail:" +msgstr "E-mail:" + +msgid "Roles:" +msgstr "Roles:" + +msgid "Invalid e-mail address value." +msgstr "Invalid e-mail address value." + +msgid "Invalid username value." +msgstr "Invalid username value." + +msgid "Username" +msgstr "Username" + +msgid "E-mail" +msgstr "E-mail" + +msgid "Description" +msgstr "Description" + +msgid "Add user" +msgstr "Add user" + +msgid "Username field cannot be empty." +msgstr "Username field cannot be empty." + +msgid "At least one role must be selected." +msgstr "At least one role must be selected." + +msgid "Username with the given name already exists." +msgstr "Username with the given name already exists." + +msgid "Edit role" +msgstr "Edit role" + +msgid "Role with limitted access" +msgstr "Role with limitted access" + +msgid "Role with full access" +msgstr "Role with full access" + +msgid "Role:" +msgstr "Role:" + +msgid "Resources:" +msgstr "Resources:" + +msgid "Role name" +msgstr "Role name" + +msgid "Resources" +msgstr "Resources" + +msgid "Add new role" +msgstr "Add new role" + +msgid "Role field cannot be empty." +msgstr "Role field cannot be empty." + +msgid "At least one resource must be selected." +msgstr "At least one resource must be selected." + +msgid "Role with the given name already exists." +msgstr "Role with the given name already exists." + +msgid "This is native predefined role, that cannot be changed. For having custom roles please use the button to add new role." +msgstr "This is native predefined role, that cannot be changed. For having custom roles please use the button to add new role." + +msgid "The following roles are predefined and cannot be removed: %predefined_roles" +msgstr "The following roles are predefined and cannot be removed: %predefined_roles" + +msgid "The following roles are using by users and cannot be removed: %used_roles To remove them, please unassign all users from these roles." +msgstr "The following roles are using by users and cannot be removed: %used_roles To remove them, please unassign all users from these roles." + +msgid "Add role" +msgstr "Add role" + +msgid "Authentication method" +msgstr "Authentication method" + +msgid "HTTP Basic authentication" +msgstr "HTTP Basic authentication" + +msgid "LDAP authentication" +msgstr "LDAP authentication" + +msgid "LDAP server options" +msgstr "LDAP server options" + +msgid "SSL encryption (LDAPS):" +msgstr "SSL encryption (LDAPS):" + +msgid "Protocol version:" +msgstr "Protocol version:" + +msgid "LDAP version" +msgstr "LDAP version" + +msgid "LDAP authentication method" +msgstr "LDAP authentication method" + +msgid "Anonymous authentication" +msgstr "Anonymous authentication" + +msgid "Simple authentication" +msgstr "Simple authentication" + +msgid "Base DN:" +msgstr "Base DN:" + +msgid "Attributes" +msgstr "Attributes" + +msgid "Please enter port." +msgstr "Please enter port." + +msgid "Please enter username." +msgstr "Please enter username." + +msgid "Manager DN:" +msgstr "Manager DN:" + +msgid "Please enter IP Address/Hostname." +msgstr "Please enter IP Address/Hostname." + +msgid "Please enter username attribute." +msgstr "Please enter username attribute." + +msgid "Test LDAP connection" +msgstr "Test LDAP connection" + +msgid "Manage LDAP users" +msgstr "Manage LDAP users" + +msgid "Please enter Base DN." +msgstr "Please enter Base DN." + +msgid "LDAP user list" +msgstr "LDAP user list" + +msgid "Import users" +msgstr "Import users" + +msgid "Import all users" +msgstr "Import all users" + +msgid "Import selected users" +msgstr "Import selected users" + +msgid "Import users whose fulfill criteria" +msgstr "Import users whose fulfill criteria" + +msgid "Import options:" +msgstr "Import options:" + +msgid "Criteria filter:" +msgstr "Criteria filter:" + +msgid "contains:" +msgstr "contains:" + +msgid "Reload" +msgstr "Reload" + +msgid "Please select users in table to import." +msgstr "Please select users in table to import." + +msgid "Do not overwrite existing Baculum Web users, if they have the same username" +msgstr "Do not overwrite existing Baculum Web users, if they have the same username" + +msgid "Default role for imported users:" +msgstr "Default role for imported users:" + +msgid "Advanced options" +msgstr "Advanced options" + +msgid "Default API host for imported users:" +msgstr "Default API host for imported users:" + +msgid "Log in" +msgstr "Log in" + +msgid "Invalid username or password" +msgstr "Invalid username or password" + +msgid "IP address restrictions" +msgstr "IP address restrictions" + +msgid "IP address restrictions:" +msgstr "IP address restrictions:" + +msgid "Use CTRL + left-click to multiple item selection" +msgstr "Use CTRL + left-click to multiple item selection" + +msgid "Comma separated IP addresses. Using asterisk character, there is also possible to provide subnet, for example: 192.168.1.*" +msgstr "Comma separated IP addresses. Using asterisk character, there is also possible to provide subnet, for example: 192.168.1.*" + +msgid "Default IP address restrictions for imported users:" +msgstr "Default IP address restrictions for imported users:" + +msgid "Set your IP address" +msgstr "Set your IP address" + +msgid "Invalid IP address restrictions value. This field can have comma separated IP addresses only or subnet addresses like 192.168.1.*" +msgstr "Invalid IP address restrictions value. This field can have comma separated IP addresses only or subnet addresses like 192.168.1.*" + +msgid "This type of authentication is realized by web server. To use it, the basic authentication has to be enabled in the Baculum Web web server configuration." +msgstr "This type of authentication is realized by web server. To use it, the basic authentication has to be enabled in the Baculum Web web server configuration." + +msgid "General settings" +msgstr "General settings" + +msgid "Default access setting for logged in users not defined in Baculum Web:" +msgstr "Default access setting for logged in users not defined in Baculum Web:" + +msgid "No access" +msgstr "No access" + +msgid "Access with default settings" +msgstr "Access with default settings" + +msgid "Default role:" +msgstr "Default role:" + +msgid "Default API host:" +msgstr "Default API host:" + +msgid "Users file path:" +msgstr "Users file path:" + +msgid "Hash algorithm:" +msgstr "Hash algorithm:" + +msgid "Please enter users file path value." +msgstr "Please enter users file path value." + +msgid "Test" +msgstr "Test" + +msgid "The user file is not accessible." +msgstr "The user file is not accessible." + +msgid "The user file is not readable by web server user." +msgstr "The user file is not readable by web server user." + +msgid "The user file is readable but not writeable by web server user." +msgstr "The user file is readable but not writeable by web server user." + +msgid "Allow Baculum Web to manage the Basic authentication users (add/remove users and change their passwords)" +msgstr "Allow Baculum Web to manage the Basic authentication users (add/remove users and change their passwords)" + +msgid "Authorization failed. Please contact the Baculum administrator to grant permissions." +msgstr "Authorization failed. Please contact the Baculum administrator to grant permissions." + +msgid "Try again" +msgstr "Try again" + +msgid "Manage Basic users" +msgstr "Manage Basic users" + +msgid "Empty user list" +msgstr "Empty user list" + +msgid "Basic user list" +msgstr "Basic user list" + +msgid "Access to Baculum API" +msgstr "Access to Baculum API" + +msgid "Access to Baculum Web" +msgstr "Access to Baculum Web" + +msgid "Requried support from web server side - e.g. %supported_web_server_list" +msgstr "Requried support from web server side - e.g. %supported_web_server_list" + +msgid "Apache 2.4 with apr-util 1.5+" +msgstr "Apache 2.4 with apr-util 1.5+" + +msgid "Apache, Lighttpd and Nginx on most UNIX platforms" +msgstr "Apache, Lighttpd and Nginx on most UNIX platforms" + +msgid "Nginx on most UNIX platforms" +msgstr "Nginx on most UNIX platforms" diff --git a/gui/baculum/protected/Web/Lang/pl/messages.mo b/gui/baculum/protected/Web/Lang/pl/messages.mo index 50d30a7a8..6490c924f 100644 Binary files a/gui/baculum/protected/Web/Lang/pl/messages.mo and b/gui/baculum/protected/Web/Lang/pl/messages.mo differ diff --git a/gui/baculum/protected/Web/Lang/pl/messages.po b/gui/baculum/protected/Web/Lang/pl/messages.po index 7b8e91a9c..aa237d112 100644 --- a/gui/baculum/protected/Web/Lang/pl/messages.po +++ b/gui/baculum/protected/Web/Lang/pl/messages.po @@ -1216,12 +1216,6 @@ msgstr "Wspierane" msgid "Not supported" msgstr "Nie wspierane" -msgid "Authorization to Baculum API" -msgstr "Autoryzacja do Baculum API" - -msgid "Authorization to Baculum Web" -msgstr "Autoryzacja do Baculum Web" - msgid "Step 3 - authentication params to Baculum Web panel" msgstr "Step 3 - parametry autentykacji do panelu Baculum Web" @@ -1453,9 +1447,6 @@ msgstr "Wolumen" msgid "During restore there will be used following volumes:" msgstr "Podczas zadania przywracania danych potrzebne będą następujące wolumeny:" -msgid "Use Ctrl + Mouse Click to change selection" -msgstr "Aby zmienić wybór, użyj Ctrl + klik myszy" - msgid "Go back" msgstr "Powrót" @@ -2637,3 +2628,306 @@ msgstr "Tip: Użyj lewego przycisku myszy, aby wybrać wiersz tabeli. Użyj CTRL msgid "Show time in job log:" msgstr "Pokaż czas w dzienniku zadania:" + +msgid "Security" +msgstr "Bezpieczeństwo" + +msgid "Roles" +msgstr "Role" + +msgid "Long name" +msgstr "Długa nazwa" + +msgid "Edit user" +msgstr "Edytuj użytkownika" + +msgid "Long name:" +msgstr "Długa nazwa:" + +msgid "Description:" +msgstr "Opis:" + +msgid "E-mail:" +msgstr "E-mail:" + +msgid "Roles:" +msgstr "Roles:" + +msgid "Invalid e-mail address value." +msgstr "Niepoprawna wartość adresu e-mail." + +msgid "Invalid username value." +msgstr "Niepoprawna nazwa użytkownika." + +msgid "Username" +msgstr "Użytkownik" + +msgid "E-mail" +msgstr "E-mail" + +msgid "Description" +msgstr "Opis" + +msgid "Add user" +msgstr "Dodaj użytkownika" + +msgid "Username field cannot be empty." +msgstr "Pole użytkownika nie może być puste." + +msgid "At least one role must be selected." +msgstr "Co najmniej jednak rola musi być wybrana." + +msgid "Username with the given name already exists." +msgstr "Użytkownik z podaną nazwą już istnieje." + +msgid "Edit role" +msgstr "Edytuj rolę" + +msgid "Role with limitted access" +msgstr "Rola z ograniczonym dostępem" + +msgid "Role with full access" +msgstr "Rola z pełnym dostępem" + +msgid "Role:" +msgstr "Rola:" + +msgid "Resources:" +msgstr "Zasoby:" + +msgid "Role name" +msgstr "Rola" + +msgid "Resources" +msgstr "Zasoby" + +msgid "Add new role" +msgstr "Dodaj nową rolę" + +msgid "Role field cannot be empty." +msgstr "Pole roli nie może być puste." + +msgid "At least one resource must be selected." +msgstr "Musisz wybrać co najmniej jeden zasób." + +msgid "Role with the given name already exists." +msgstr "Rola z podaną nazwą już istnieje." + +msgid "This is native predefined role, that cannot be changed. For having custom roles, please use the button to add new role." +msgstr "To jest natywna predefiniowana rola, która nie może zostać zmieniona. Aby mieć własne niestandardowe role, proszę użyć przycisku do dodawania nowej roli." + +msgid "The following roles are predefined and cannot be removed: %predefined_roles" +msgstr "Następujące role są natywne predefiniowane i nie mogą zostać usunięte: %predefined_roles" + +msgid "The following roles are using by users and cannot be removed: %used_roles To remove them, please unassign all users from these roles." +msgstr "Następujące role są używane przez użytkowników i nie mogą zostać usunięte: %used_roles Aby je usunąć, wypisz wszystkich" + +msgid "Add role" +msgstr "Add role" + +msgid "Authentication method" +msgstr "Metoda uwierzytelniania" + +msgid "HTTP Basic authentication" +msgstr "Uwierzytelnianie HTTP Basic" + +msgid "LDAP authentication" +msgstr "Uwierzytelnianie LDAP" + +msgid "LDAP server options" +msgstr "Opcje serwera LDAP" + +msgid "SSL encryption (LDAPS):" +msgstr "Szyfrowanie SSL (LDAPS):" + +msgid "Protocol version:" +msgstr "Wersja protokołu:" + +msgid "LDAP version" +msgstr "LDAP wersja" + +msgid "LDAP authentication method" +msgstr "Metoda uwierzytelniania LDAP" + +msgid "Anonymous authentication" +msgstr "Uwierzytelnianie anonimowe" + +msgid "Simple authentication" +msgstr "Proste uwierzytelnianie" + +msgid "Base DN:" +msgstr "Bazowy DN:" + +msgid "Attributes" +msgstr "Atrybuty" + +msgid "Please enter port." +msgstr "Proszę wprowadzić port." + +msgid "Please enter username." +msgstr "Proszę wprowadzić nazwę użytkownika." + +msgid "Manager DN:" +msgstr "DN konta administratora:" + +msgid "Please enter IP Address/Hostname." +msgstr "Proszę wprowadzić adres IP/nazwę hosta." + +msgid "Please enter username attribute." +msgstr "Proszę wprowadzić atrybut użytkownika." + +msgid "Test LDAP connection" +msgstr "Test połączenia LDAP" + +msgid "Manage LDAP users" +msgstr "Zarządzaj użytkowników LDAP" + +msgid "Please enter Base DN." +msgstr "Proszę wprowadzić Bazowy DN." + +msgid "LDAP user list" +msgstr "Lista użytkowników LDAP" + +msgid "Import users" +msgstr "Importuj użytkowników" + +msgid "Import all users" +msgstr "Importuj wszystkich użytkowników" + +msgid "Import selected users" +msgstr "Importuj wybranych użytkowników" + +msgid "Import users whose fulfill criteria" +msgstr "Importuj użytkowników spełniających kryteria" + +msgid "Import options:" +msgstr "Opcje importu:" + +msgid "Criteria filter:" +msgstr "Filtr kryteriów:" + +msgid "contains:" +msgstr "zawiera:" + +msgid "Reload" +msgstr "Przeładuj" + +msgid "Please select users in table to import." +msgstr "Proszę wybrać użytkowników w tabeli do zaimportowania." + +msgid "Do not overwrite existing Baculum Web users, if they have the same username" +msgstr "Nie nadpisuj istniejących użytkowników Baculum Web, jeśli mają tą samą nazwę" + +msgid "Default role for imported users:" +msgstr "Domyślna rola dla zaimportowanych użytkowników:" + +msgid "Advanced options" +msgstr "Opcje zaawansowane" + +msgid "Default API host for imported users:" +msgstr "Domyślny host API dla zaimportowanych użytkowników:" + +msgid "Log in" +msgstr "Zaloguj" + +msgid "Invalid username or password" +msgstr "Niepoprawna nazwa użytkownika lub hasło" + +msgid "IP address restrictions" +msgstr "IP address restrictions" + +msgid "IP address restrictions:" +msgstr "IP address restrictions:" + +msgid "Use CTRL + left-click to multiple item selection" +msgstr "Aby zaznaczyć wiele elementów, użyj CTRL + lewy przycisk myszy" + +msgid "Comma separated IP addresses. Using asterisk character, there is also possible to provide subnet, for example: 192.168.1.*" +msgstr "Adresy IP oddzielone przecinkami. Używając znaku gwiazdki, jest również możliwe d" + +msgid "Default IP address restrictions for imported users:" +msgstr "Domyślne restrykcje adresu IP dla zaimportowanych użytkowników:" + +msgid "Set your IP address" +msgstr "Ustaw twój adres IP" + +msgid "Invalid IP address restrictions value. This field can have comma separated IP addresses only or subnet addresses like 192.168.1.*" +msgstr "Nieprawidłowa wartość restrykcji adresów IP. To pole może mieć tylko oddzielone przecinkami adresy IP lub adresy podsieci, takie jak 192.168.1.*" + +msgid "This type of authentication is realized by web server. To use it, the basic authentication has to be enabled in the Baculum Web web server configuration." +msgstr "Ten typ uwierzytelniania jest realizowany przez serwer WWW. Aby z niego skorzystać, uwierzytelnianie 'basic' musi być włączone w konfiguracji serwera WWW." + +msgid "General settings" +msgstr "Ustawienia ogólne" + +msgid "Default access setting for logged in users not defined in Baculum Web:" +msgstr "Domyślne ustawienie dostępu dla zalogowanych użytkowników nie zdefiniowanych w Baculum Web:" + +msgid "No access" +msgstr "Brak dostępu" + +msgid "Access with default settings" +msgstr "Dostęp z domyślnymi ustawieniami" + +msgid "Default role:" +msgstr "Domyślna rola:" + +msgid "Default API host:" +msgstr "Domyślny host API:" + +msgid "Users file path:" +msgstr "Lokalizacja pliku użytkowników:" + +msgid "Hash algorithm:" +msgstr "Funkcja skrótu:" + +msgid "Please enter users file path value." +msgstr "Proszę wprowadzić lokalizację pliku użytkowników." + +msgid "Test" +msgstr "Test" + +msgid "The user file is not accessible." +msgstr "Plik użytkownika nie jest dostępny." + +msgid "The user file is not readable by web server user." +msgstr "Plik użytkowników nie jest możliwy do odczytania przez użytkownika serwera WWW." + +msgid "The user file is readable but not writeable by web server user." +msgstr "Plik użytkowników jest możliwy do odczytania, ale nie jest możliwy do zapisania przez użytkownika serwera WWW." + +msgid "Allow Baculum Web to manage the Basic authentication users (add/remove users and change their passwords)" +msgstr "Allow Baculum Web to manage the Basic authentication users (add/remove users and change their passwords)" + +msgid "Authorization failed. Please contact the Baculum administrator to grant permissions." +msgstr "Błąd autoryzacji. Proszę skontaktować się administratorem Baculum w celu nadania uprawnień." + +msgid "Try again" +msgstr "Spróbuj ponownie" + +msgid "Manage Basic users" +msgstr "Zarządzaj użytkownikami Basic" + +msgid "Empty user list" +msgstr "Pusta lista użytkowników" + +msgid "Basic user list" +msgstr "Lista użytkowników Basic" + +msgid "Access to Baculum API" +msgstr "Dostęp do Baculum API" + +msgid "Access to Baculum Web" +msgstr "Dostęp do Baculum Web" + +msgid "Requried support from web server side - e.g. %supported_web_server_list" +msgstr "Wymagane wsparcie ze strony serwera WWW - m.in. %supported_web_server_list" + +msgid "Apache 2.4 with apr-util 1.5+" +msgstr "Apache 2.4 z apr-util 1.5+" + +msgid "Apache, Lighttpd and Nginx on most UNIX platforms" +msgstr "Apache, Lighttpd i Nginx na większości platform UNIXowych" + +msgid "Nginx on most UNIX platforms" +msgstr "Nginx na większości platform UNIXowych" diff --git a/gui/baculum/protected/Web/Lang/pt/messages.mo b/gui/baculum/protected/Web/Lang/pt/messages.mo index 927ea42a3..48f2718d3 100644 Binary files a/gui/baculum/protected/Web/Lang/pt/messages.mo and b/gui/baculum/protected/Web/Lang/pt/messages.mo differ diff --git a/gui/baculum/protected/Web/Lang/pt/messages.po b/gui/baculum/protected/Web/Lang/pt/messages.po index 78cb1bf94..8b31dd6e2 100644 --- a/gui/baculum/protected/Web/Lang/pt/messages.po +++ b/gui/baculum/protected/Web/Lang/pt/messages.po @@ -1216,12 +1216,6 @@ msgstr "Suportado" msgid "Not supported" msgstr "Não suportado" -msgid "Authorization to Baculum API" -msgstr "Autorização para a API do Baculum" - -msgid "Authorization to Baculum Web" -msgstr "Autorização para o Baculum Web" - msgid "Step 3 - authentication params to Baculum Web panel" msgstr "Etapa 3 - Parâmetros de autenticação para o painel Web do Baculum" @@ -1453,9 +1447,6 @@ msgstr "Volume" msgid "During restore there will be used following volumes:" msgstr "Durante a restauração, serão utilizados os seguintes volumes:" -msgid "Use Ctrl + Mouse Click to change selection" -msgstr "Use Ctrl + Mouse Click para alterar a seleção" - msgid "Go back" msgstr "Voltar" @@ -2637,3 +2628,306 @@ msgstr "Tip: Use left-click to select table row. Use CTRL + left-click to multip msgid "Show time in job log:" msgstr "Show time in job log:" + +msgid "Security" +msgstr "Security" + +msgid "Roles" +msgstr "Roles" + +msgid "Long name" +msgstr "Long name" + +msgid "Edit user" +msgstr "Edit user" + +msgid "Long name:" +msgstr "Long name:" + +msgid "Description:" +msgstr "Description:" + +msgid "E-mail:" +msgstr "E-mail:" + +msgid "Roles:" +msgstr "Roles:" + +msgid "Invalid e-mail address value." +msgstr "Invalid e-mail address value." + +msgid "Invalid username value." +msgstr "Invalid username value." + +msgid "Username" +msgstr "Username" + +msgid "E-mail" +msgstr "E-mail" + +msgid "Description" +msgstr "Description" + +msgid "Add user" +msgstr "Add user" + +msgid "Username field cannot be empty." +msgstr "Username field cannot be empty." + +msgid "At least one role must be selected." +msgstr "At least one role must be selected." + +msgid "Username with the given name already exists." +msgstr "Username with the given name already exists." + +msgid "Edit role" +msgstr "Edit role" + +msgid "Role with limitted access" +msgstr "Role with limitted access" + +msgid "Role with full access" +msgstr "Role with full access" + +msgid "Role:" +msgstr "Role:" + +msgid "Resources:" +msgstr "Resources:" + +msgid "Role name" +msgstr "Role name" + +msgid "Resources" +msgstr "Resources" + +msgid "Add new role" +msgstr "Add new role" + +msgid "Role field cannot be empty." +msgstr "Role field cannot be empty." + +msgid "At least one resource must be selected." +msgstr "At least one resource must be selected." + +msgid "Role with the given name already exists." +msgstr "Role with the given name already exists." + +msgid "This is native predefined role, that cannot be changed. For having custom roles please use the button to add new role." +msgstr "This is native predefined role, that cannot be changed. For having custom roles please use the button to add new role." + +msgid "The following roles are predefined and cannot be removed: %predefined_roles" +msgstr "The following roles are predefined and cannot be removed: %predefined_roles" + +msgid "The following roles are using by users and cannot be removed: %used_roles To remove them, please unassign all users from these roles." +msgstr "The following roles are using by users and cannot be removed: %used_roles To remove them, please unassign all users from these roles." + +msgid "Add role" +msgstr "Add role" + +msgid "Authentication method" +msgstr "Authentication method" + +msgid "HTTP Basic authentication" +msgstr "HTTP Basic authentication" + +msgid "LDAP authentication" +msgstr "LDAP authentication" + +msgid "LDAP server options" +msgstr "LDAP server options" + +msgid "SSL encryption (LDAPS):" +msgstr "SSL encryption (LDAPS):" + +msgid "Protocol version:" +msgstr "Protocol version:" + +msgid "LDAP version" +msgstr "LDAP version" + +msgid "LDAP authentication method" +msgstr "LDAP authentication method" + +msgid "Anonymous authentication" +msgstr "Anonymous authentication" + +msgid "Simple authentication" +msgstr "Simple authentication" + +msgid "Base DN:" +msgstr "Base DN:" + +msgid "Attributes" +msgstr "Attributes" + +msgid "Please enter port." +msgstr "Please enter port." + +msgid "Please enter username." +msgstr "Please enter username." + +msgid "Manager DN:" +msgstr "Manager DN:" + +msgid "Please enter IP Address/Hostname." +msgstr "Please enter IP Address/Hostname." + +msgid "Please enter username attribute." +msgstr "Please enter username attribute." + +msgid "Test LDAP connection" +msgstr "Test LDAP connection" + +msgid "Manage LDAP users" +msgstr "Manage LDAP users" + +msgid "Please enter Base DN." +msgstr "Please enter Base DN." + +msgid "LDAP user list" +msgstr "LDAP user list" + +msgid "Import users" +msgstr "Import users" + +msgid "Import all users" +msgstr "Import all users" + +msgid "Import selected users" +msgstr "Import selected users" + +msgid "Import users whose fulfill criteria" +msgstr "Import users whose fulfill criteria" + +msgid "Import options:" +msgstr "Import options:" + +msgid "Criteria filter:" +msgstr "Criteria filter:" + +msgid "contains:" +msgstr "contains:" + +msgid "Reload" +msgstr "Reload" + +msgid "Please select users in table to import." +msgstr "Please select users in table to import." + +msgid "Do not overwrite existing Baculum Web users, if they have the same username" +msgstr "Do not overwrite existing Baculum Web users, if they have the same username" + +msgid "Default role for imported users:" +msgstr "Default role for imported users:" + +msgid "Advanced options" +msgstr "Advanced options" + +msgid "Default API host for imported users:" +msgstr "Default API host for imported users:" + +msgid "Log in" +msgstr "Log in" + +msgid "Invalid username or password" +msgstr "Invalid username or password" + +msgid "IP address restrictions" +msgstr "IP address restrictions" + +msgid "IP address restrictions:" +msgstr "IP address restrictions:" + +msgid "Use CTRL + left-click to multiple item selection" +msgstr "Use CTRL + left-click to multiple item selection" + +msgid "Comma separated IP addresses. Using asterisk character, there is also possible to provide subnet, for example: 192.168.1.*" +msgstr "Comma separated IP addresses. Using asterisk character, there is also possible to provide subnet, for example: 192.168.1.*" + +msgid "Default IP address restrictions for imported users:" +msgstr "Default IP address restrictions for imported users:" + +msgid "Set your IP address" +msgstr "Set your IP address" + +msgid "Invalid IP address restrictions value. This field can have comma separated IP addresses only or subnet addresses like 192.168.1.*" +msgstr "Invalid IP address restrictions value. This field can have comma separated IP addresses only or subnet addresses like 192.168.1.*" + +msgid "This type of authentication is realized by web server. To use it, the basic authentication has to be enabled in the Baculum Web web server configuration." +msgstr "This type of authentication is realized by web server. To use it, the basic authentication has to be enabled in the Baculum Web web server configuration." + +msgid "General settings" +msgstr "General settings" + +msgid "Default access setting for logged in users not defined in Baculum Web:" +msgstr "Default access setting for logged in users not defined in Baculum Web:" + +msgid "No access" +msgstr "No access" + +msgid "Access with default settings" +msgstr "Access with default settings" + +msgid "Default role:" +msgstr "Default role:" + +msgid "Default API host:" +msgstr "Default API host:" + +msgid "Users file path:" +msgstr "Users file path:" + +msgid "Hash algorithm:" +msgstr "Hash algorithm:" + +msgid "Please enter users file path value." +msgstr "Please enter users file path value." + +msgid "Test" +msgstr "Test" + +msgid "The user file is not accessible." +msgstr "The user file is not accessible." + +msgid "The user file is not readable by web server user." +msgstr "The user file is not readable by web server user." + +msgid "The user file is readable but not writeable by web server user." +msgstr "The user file is readable but not writeable by web server user." + +msgid "Allow Baculum Web to manage the Basic authentication users (add/remove users and change their passwords)" +msgstr "Allow Baculum Web to manage the Basic authentication users (add/remove users and change their passwords)" + +msgid "Authorization failed. Please contact the Baculum administrator to grant permissions." +msgstr "Authorization failed. Please contact the Baculum administrator to grant permissions." + +msgid "Try again" +msgstr "Try again" + +msgid "Manage Basic users" +msgstr "Manage Basic users" + +msgid "Empty user list" +msgstr "Empty user list" + +msgid "Basic user list" +msgstr "Basic user list" + +msgid "Access to Baculum API" +msgstr "Access to Baculum API" + +msgid "Access to Baculum Web" +msgstr "Access to Baculum Web" + +msgid "Requried support from web server side - e.g. %supported_web_server_list" +msgstr "Requried support from web server side - e.g. %supported_web_server_list" + +msgid "Apache 2.4 with apr-util 1.5+" +msgstr "Apache 2.4 with apr-util 1.5+" + +msgid "Apache, Lighttpd and Nginx on most UNIX platforms" +msgstr "Apache, Lighttpd and Nginx on most UNIX platforms" + +msgid "Nginx on most UNIX platforms" +msgstr "Nginx on most UNIX platforms" diff --git a/gui/baculum/protected/Web/Layouts/Simple.php b/gui/baculum/protected/Web/Layouts/Simple.php new file mode 100644 index 000000000..f4eeb8891 --- /dev/null +++ b/gui/baculum/protected/Web/Layouts/Simple.php @@ -0,0 +1,34 @@ + + * @category Layout + * @package Baculum Web + */ +class Simple extends TTemplateControl { +} +?> diff --git a/gui/baculum/protected/Web/Layouts/Simple.tpl b/gui/baculum/protected/Web/Layouts/Simple.tpl new file mode 100644 index 000000000..6ad414633 --- /dev/null +++ b/gui/baculum/protected/Web/Layouts/Simple.tpl @@ -0,0 +1,21 @@ + + + + + + + + + + /> + +
+ + + +
+ + +
+ + diff --git a/gui/baculum/protected/Web/Pages/ApplicationSettings.php b/gui/baculum/protected/Web/Pages/ApplicationSettings.php index c32934eb5..f3729bf79 100644 --- a/gui/baculum/protected/Web/Pages/ApplicationSettings.php +++ b/gui/baculum/protected/Web/Pages/ApplicationSettings.php @@ -33,8 +33,6 @@ Prado::using('Application.Web.Pages.Monitor'); */ class ApplicationSettings extends BaculumWebPage { - protected $admin = true; - public function onInit($param) { parent::onInit($param); $this->DecimalBytes->Checked = true; diff --git a/gui/baculum/protected/Web/Pages/ConfigureHosts.php b/gui/baculum/protected/Web/Pages/ConfigureHosts.php index eb53cf7b4..720ae1097 100644 --- a/gui/baculum/protected/Web/Pages/ConfigureHosts.php +++ b/gui/baculum/protected/Web/Pages/ConfigureHosts.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -31,8 +31,6 @@ Prado::using('Application.Web.Class.BaculumWebPage'); */ class ConfigureHosts extends BaculumWebPage { - protected $admin = true; - public function onInit($param) { parent::onInit($param); if ($this->IsPostBack || $this->IsCallBack) { diff --git a/gui/baculum/protected/Web/Pages/FileSetList.php b/gui/baculum/protected/Web/Pages/FileSetList.php index 9f6362a11..5ea186f88 100644 --- a/gui/baculum/protected/Web/Pages/FileSetList.php +++ b/gui/baculum/protected/Web/Pages/FileSetList.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -33,8 +33,6 @@ class FileSetList extends BaculumWebPage { const USE_CACHE = true; - protected $admin = true; - public $filesets = array(); public function onInit($param) { diff --git a/gui/baculum/protected/Web/Pages/FileSetView.php b/gui/baculum/protected/Web/Pages/FileSetView.php index 49cb31106..cf657cc60 100644 --- a/gui/baculum/protected/Web/Pages/FileSetView.php +++ b/gui/baculum/protected/Web/Pages/FileSetView.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -33,8 +33,6 @@ class FileSetView extends BaculumWebPage { const USE_CACHE = true; - protected $admin = true; - const FILESET_NAME = 'FileSetName'; public function onInit($param) { diff --git a/gui/baculum/protected/Web/Pages/LoginPage.page b/gui/baculum/protected/Web/Pages/LoginPage.page new file mode 100644 index 000000000..e0298215b --- /dev/null +++ b/gui/baculum/protected/Web/Pages/LoginPage.page @@ -0,0 +1,44 @@ +<%@ MasterClass="Application.Web.Layouts.Simple" Theme="Baculum-v2"%> + +
+ + Baculum - The Bacula web interface + <%[ Invalid username or password ]%> +
+ +
+
+ +
+
+ +  <%[ Log in ]%> + +
+
+ + Baculum - The Bacula web interface +

<%[ Authorization failed. Please contact the Baculum administrator to grant permissions. ]%>

+

+ + + + if (!window.chrome || window.navigator.webdriver) { + window.location.href = main_side_bar_reload_url; + } else if (window.chrome) { + // For chrome this reload is required to show login Basic auth prompt + window.location.reload(); + } + + <%[ Logout ]%> + +  <%[ Try again ]%> +

+
+
+ +
diff --git a/gui/baculum/protected/Web/Pages/LoginPage.php b/gui/baculum/protected/Web/Pages/LoginPage.php new file mode 100644 index 000000000..76132dbf9 --- /dev/null +++ b/gui/baculum/protected/Web/Pages/LoginPage.php @@ -0,0 +1,125 @@ + + * @category Page + * @package Baculum Web + */ +class LoginPage extends BaculumWebPage { + + /** + * Reload URL is used to refresh page after logout with Basic auth. + */ + public $reload_url = ''; + + public function onInit($param) { + parent::onInit($param); + if ($this->getModule('web_config')->isAuthMethodBasic()) { + $fake_pwd = $this->getModule('crypto')->getRandomString(); + $user = $_SERVER['PHP_AUTH_USER'] . '1'; // must be different than currently logged in Basic user + + // do a login try with different user and password to logout current user + $this->reload_url = $this->getPage()->getFullLoginUrl($user, $fake_pwd); + } + } + + public function onPreLoad($param) { + parent::onPreLoad($param); + $users = $this->getModule('users'); + + $authorized = $users->isAuthorized(); + + if ($this->User->getIsGuest() === false) { + // for authenticated users + + $web_config = $this->getModule('web_config'); + if ($web_config->isAuthMethodBasic()) { + /** + * Using default page here is because it is required for case if user doesn't + * have access to default service page. Then it is directed to first free page + * available for him. + */ + if ($authorized || ($this->User->Enabled && $users->getAuthorizedFlag() === $this->Service->DefaultPage)) { + // Basic user authenticated and authorized + $this->goToDefaultPage(); + } else { + // Basic - user authenticated but not authorized or no access by default + $this->LoginForm->Display = 'None'; + $this->AuthorizationError->Display = 'Dynamic'; + } + } else if ($web_config->isAuthMethodLdap() && !$authorized) { + // Ldap - user authenticated but not authorized + $this->LoginForm->Display = 'None'; + $this->AuthorizationError->Display = 'Dynamic'; + } + } + } + + /** + * Login using login page form. + * + * @param TLinkButton $sender sender object + * @param mixed $param event parameter (in this case null) + */ + public function login($sender, $param) { + $username = $this->Username->Text; + $password = $this->Password->Text; + + if ($this->getModule('web_config')->isAuthMethodBasic() && !empty($_SERVER['PHP_AUTH_USER'])) { + // For basic auth take username from web server. + $username = $_SERVER['PHP_AUTH_USER']; + } + + $success = $this->getModule('auth')->login($username, $password); + if ($success === true) { + $this->goToDefaultPage(); + } else { + $this->Msg->Display = 'Fixed'; + } + } + + /** + * Logout button event handler. + * It is used for logout button visible after unsuccessfull authorization. + * + * @param TLinkButton $sender sender object + * @param mixed $param event parameter (in this case null) + */ + public function logout($sender, $param) { + $this->getModule('auth')->logout(); + if ($this->getModule('web_config')->isAuthMethodBasic()) { + /** + * This status code 401 is necessary to stop comming AJAX requests + * and to bring the login prompt on. + */ + $this->Response->setStatusCode(401); + } else { + $this->goToDefaultPage(); + } + } +} +?> diff --git a/gui/baculum/protected/Web/Pages/Monitor.php b/gui/baculum/protected/Web/Pages/Monitor.php index a85c143da..835e33f02 100644 --- a/gui/baculum/protected/Web/Pages/Monitor.php +++ b/gui/baculum/protected/Web/Pages/Monitor.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -119,7 +119,7 @@ class Monitor extends BaculumPage { $error = $result; } } - if (!$error && $_SESSION['admin'] && key_exists('dbsize', $params)) { + if (!$error && key_exists('dbsize', $params)) { $result = $this->getModule('api')->get(array('dbsize')); if ($result->error === 0) { $monitor_data['dbsize'] = $result->output; diff --git a/gui/baculum/protected/Web/Pages/NewJobWizard.php b/gui/baculum/protected/Web/Pages/NewJobWizard.php index 5e16b4658..08b952ab5 100644 --- a/gui/baculum/protected/Web/Pages/NewJobWizard.php +++ b/gui/baculum/protected/Web/Pages/NewJobWizard.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -33,8 +33,6 @@ Prado::using('System.Web.UI.WebControls.TWizard'); */ class NewJobWizard extends BaculumWebPage { - protected $admin = true; - const PREV_STEP = 'PrevStep'; const JOBDEFS = 'JobDefs'; diff --git a/gui/baculum/protected/Web/Pages/NewResource.php b/gui/baculum/protected/Web/Pages/NewResource.php index 5ab843091..933132e26 100644 --- a/gui/baculum/protected/Web/Pages/NewResource.php +++ b/gui/baculum/protected/Web/Pages/NewResource.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -60,10 +60,8 @@ class NewResource extends BaculumWebPage { $this->setComponentType($component_type); $this->setComponentName($component_name); $this->setResourceType($resource_type); - if (!$_SESSION['admin']) { - // Non-admin can configure only host assigned to him - $this->NewResource->setHost($_SESSION['api_host']); - } + // Non-admin can configure only host assigned to him + $this->NewResource->setHost($this->User->getAPIHosts()); $this->NewResource->setComponentType($component_type); $this->NewResource->setComponentName($component_name); $this->NewResource->setResourceType($resource_type); @@ -80,7 +78,7 @@ class NewResource extends BaculumWebPage { $config = $this->getModule('host_config')->getConfig(); $hosts = array('' => Prado::localize('Please select host')); foreach ($config as $host => $vals) { - if (!$_SESSION['admin'] && $host !== $_SESSION['api_host']) { + if ($host !== $this->User->getAPIHosts()) { continue; } $item = "Host: $host, Address: {$vals['address']}, Port: {$vals['port']}"; diff --git a/gui/baculum/protected/Web/Pages/PoolList.php b/gui/baculum/protected/Web/Pages/PoolList.php index 35f89c016..733fec9fa 100644 --- a/gui/baculum/protected/Web/Pages/PoolList.php +++ b/gui/baculum/protected/Web/Pages/PoolList.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -30,7 +30,5 @@ Prado::using('Application.Web.Class.BaculumWebPage'); * @package Baculum Web */ class PoolList extends BaculumWebPage { - - protected $admin = true; } ?> diff --git a/gui/baculum/protected/Web/Pages/PoolView.php b/gui/baculum/protected/Web/Pages/PoolView.php index a80d4174b..96ecd8791 100644 --- a/gui/baculum/protected/Web/Pages/PoolView.php +++ b/gui/baculum/protected/Web/Pages/PoolView.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -38,8 +38,6 @@ class PoolView extends BaculumWebPage { const POOLID = 'PoolId'; const POOL_NAME = 'PoolName'; - protected $admin = true; - public $volumes_in_pool; public function onInit($param) { diff --git a/gui/baculum/protected/Web/Pages/Requirements.php b/gui/baculum/protected/Web/Pages/Requirements.php index 9ae2cb933..4325a73a1 100644 --- a/gui/baculum/protected/Web/Pages/Requirements.php +++ b/gui/baculum/protected/Web/Pages/Requirements.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -38,12 +38,16 @@ class Requirements extends GeneralRequirements { * translation engine initialization. From this reason all messages are not * translated. */ - private $req_exts = array( - array( + private $req_exts = [ + [ 'ext' => 'curl', 'help_msg' => 'Please install PHP cURL module.' - ) - ); + ], + [ + 'ext' => 'ldap', + 'help_msg' => 'Please install PHP LDAP module.' + ] + ]; public function __construct($app_dir, $base_dir) { parent::__construct($app_dir, $base_dir); diff --git a/gui/baculum/protected/Web/Pages/ScheduleList.php b/gui/baculum/protected/Web/Pages/ScheduleList.php index 678924221..f2463f884 100644 --- a/gui/baculum/protected/Web/Pages/ScheduleList.php +++ b/gui/baculum/protected/Web/Pages/ScheduleList.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -33,8 +33,6 @@ class ScheduleList extends BaculumWebPage { const USE_CACHE = true; - protected $admin = true; - public $schedules = array(); public function onInit($param) { diff --git a/gui/baculum/protected/Web/Pages/ScheduleView.php b/gui/baculum/protected/Web/Pages/ScheduleView.php index 8de37cc9c..58c6e998c 100644 --- a/gui/baculum/protected/Web/Pages/ScheduleView.php +++ b/gui/baculum/protected/Web/Pages/ScheduleView.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -35,8 +35,6 @@ class ScheduleView extends BaculumWebPage { const SCHEDULE_NAME = 'ScheduleName'; - protected $admin = true; - public function onInit($param) { parent::onInit($param); if ($this->IsPostBack || $this->IsCallBack) { diff --git a/gui/baculum/protected/Web/Pages/Security.page b/gui/baculum/protected/Web/Pages/Security.page new file mode 100644 index 000000000..5b2996ae5 --- /dev/null +++ b/gui/baculum/protected/Web/Pages/Security.page @@ -0,0 +1,1844 @@ +<%@ MasterClass="Application.Web.Layouts.Main" Theme="Baculum-v2"%> + + +
+
+ <%[ Security ]%> +
+
+
+ + + +
+ +
+

<%[ General settings ]%>

+

<%[ Default access setting for logged in users not defined in Baculum Web: ]%>

+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ <%[ Default role: ]%> +
+
+ +
+
+
+
+ <%[ Default API host: ]%> +
+
+ +
+
+
+
+ +

<%[ Authentication method ]%>

+
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+ +  <%[ Save ]%> + + document.getElementById('auth_method_save_ok').style.display = 'none'; + document.getElementById('auth_method_save_error').style.display = 'none'; + document.getElementById('auth_method_save_loading').style.visibility = 'visible'; + + + document.getElementById('auth_method_save_loading').style.visibility = 'hidden'; + + +   + + +
+ + + + +
+ + + + + +
diff --git a/gui/baculum/protected/Web/Pages/Security.php b/gui/baculum/protected/Web/Pages/Security.php new file mode 100644 index 000000000..319c916a4 --- /dev/null +++ b/gui/baculum/protected/Web/Pages/Security.php @@ -0,0 +1,1200 @@ + + * @category Page + * @package Baculum Web + */ +class Security extends BaculumWebPage { + + /** + * Modal window types for users and roles. + */ + const TYPE_ADD_WINDOW = 'add'; + const TYPE_EDIT_WINDOW = 'edit'; + + /** + * Options for import users. + */ + const IMPORT_OPT_ALL_USERS = 0; + const IMPORT_OPT_SELECTED_USERS = 1; + const IMPORT_OPT_CRITERIA = 2; + + /** + * Options for import criteria. + */ + const IMPORT_CRIT_USERNAME = 0; + const IMPORT_CRIT_LONG_NAME = 1; + const IMPORT_CRIT_DESCRIPTION = 2; + const IMPORT_CRIT_EMAIL = 3; + + + /** + * Store web user config. + */ + private $user_config = []; + + /** + * Initialize page. + * + * @param mixed $param oninit event parameter + * @return none + */ + public function onInit($param) { + parent::onInit($param); + if ($this->IsCallBack || $this->IsPostBack) { + return; + } + $this->initDefAccessForm(); + $this->initAuthForm(); + $this->initUserWindow(); + $this->initRoleWindow(); + $this->setBasicAuthConfig(); + } + + /** + * Initialize form with default access settings. + * + * @return none + */ + public function initDefAccessForm() { + $this->setRoles( + $this->GeneralDefaultAccessRole, + WebUserRoles::NORMAL + ); + + $this->setAPIHosts( + $this->GeneralDefaultAccessAPIHost, + HostConfig::MAIN_CATALOG_HOST + ); + if (isset($this->web_config['security']['def_access'])) { + if ($this->web_config['security']['def_access'] === WebConfig::DEF_ACCESS_NO_ACCESS) { + $this->GeneralDefaultNoAccess->Checked = true; + } elseif ($this->web_config['security']['def_access'] === WebConfig::DEF_ACCESS_DEFAULT_SETTINGS) { + $this->GeneralDefaultAccess->Checked = true; + } + if (isset($this->web_config['security']['def_role'])) { + $this->GeneralDefaultAccessRole->SelectedValue = $this->web_config['security']['def_role']; + } + if (isset($this->web_config['security']['def_api_host'])) { + $this->GeneralDefaultAccessAPIHost->SelectedValue = $this->web_config['security']['def_api_host']; + } + } else { + $this->GeneralDefaultAccess->Checked = true; + } + } + + /** + * Initialize form with authentication method settings. + * + * @return none + */ + public function initAuthForm() { + if (isset($this->web_config['security']['auth_method'])) { + if ($this->web_config['security']['auth_method'] === WebConfig::AUTH_METHOD_BASIC) { + $this->BasicAuth->Checked = true; + } elseif ($this->web_config['security']['auth_method'] === WebConfig::AUTH_METHOD_LDAP) { + $this->LdapAuth->Checked = true; + } + + // Fill LDAP auth fileds + if (key_exists('auth_ldap', $this->web_config)) { + $this->LdapAuthServerAddress->Text = $this->web_config['auth_ldap']['address']; + $this->LdapAuthServerPort->Text = $this->web_config['auth_ldap']['port']; + $this->LdapAuthServerLdaps->Checked = ($this->web_config['auth_ldap']['ldaps'] == 1); + $this->LdapAuthServerProtocolVersion->Text = $this->web_config['auth_ldap']['protocol_ver']; + $this->LdapAuthServerBaseDn->Text = $this->web_config['auth_ldap']['base_dn']; + if ($this->web_config['auth_ldap']['auth_method'] === Ldap::AUTH_METHOD_ANON) { + $this->LdapAuthMethodAnonymous->Checked = true; + } elseif ($this->web_config['auth_ldap']['auth_method'] === Ldap::AUTH_METHOD_SIMPLE) { + $this->LdapAuthMethodSimple->Checked = true; + } + $this->LdapAuthMethodSimpleUsername->Text = $this->web_config['auth_ldap']['bind_dn']; + $this->LdapAuthMethodSimplePassword->Text = $this->web_config['auth_ldap']['bind_password']; + $this->LdapAuthServerBaseDn->Text = $this->web_config['auth_ldap']['base_dn']; + $this->LdapAttributesUsername->Text = $this->web_config['auth_ldap']['user_attr']; + $this->LdapAttributesLongName->Text = $this->web_config['auth_ldap']['long_name_attr']; + $this->LdapAttributesEmail->Text = $this->web_config['auth_ldap']['email_attr']; + $this->LdapAttributesDescription->Text = $this->web_config['auth_ldap']['desc_attr']; + } + // Fill Basic auth fields + if (key_exists('auth_basic', $this->web_config)) { + $this->BasicAuthAllowManageUsers->Checked = ($this->web_config['auth_basic']['allow_manage_users'] == 1); + $this->BasicAuthUserFile->Text = $this->web_config['auth_basic']['user_file']; + $this->BasicAuthHashAlgorithm->SelectedValue = $this->web_config['auth_basic']['hash_alg']; + } + } else { + // Default set to Basic auth method + $this->BasicAuth->Checked = true; + } + } + + /** + * Initialize values in user modal window. + * + * @return none + */ + public function initUserWindow() { + // set API hosts + $this->setAPIHosts($this->UserAPIHost); + + // set roles + $this->setRoles($this->UserRoles); + } + + /** + * Set role list control. + * + * @param object $control control which contains role list + * @param mixed $def_val default value or null if no default value to set + * @return none + */ + private function setRoles($control, $def_val = null) { + // set roles + $roles = $this->getModule('user_role')->getRoles(); + $role_items = []; + foreach ($roles as $role_name => $role) { + $role_items[$role_name] = $role['long_name'] ?: $role_name; + } + $control->DataSource = $role_items; + if ($def_val) { + $control->SelectedValue = $def_val; + } + $control->dataBind(); + } + + /** + * Set API host list control. + * + * @param object $control control which contains API host list + * @param mixed $def_val default value or null if no default value to set + * @return none + */ + private function setAPIHosts($control, $def_val = null) { + $api_hosts = array_keys($this->getModule('host_config')->getConfig()); + array_unshift($api_hosts, ''); + $control->DataSource = array_combine($api_hosts, $api_hosts); + if ($def_val) { + $control->SelectedValue = $def_val; + } + $control->dataBind(); + } + + /** + * Set and load user list. + * + * @param TCallback $sender sender object + * @param TCallbackEventParameter callback parameter + * @return none + */ + public function setUserList($sender, $param) { + $config = $this->getModule('user_config')->getConfig(); + $this->getCallbackClient()->callClientFunction('oUsers.load_user_list_cb', [ + array_values($config) + ]); + $this->user_config = $config; + } + + /** + * Load data in user modal window. + * + * @param TCallback $sender sender object + * @param TCallbackEventParameter $param callback parameter + * @return none + */ + public function loadUserWindow($sender, $param) { + //$this->getModule('user_config')->importBasicUsers(); + $username = $param->getCallbackParameter(); + $config = $this->getModule('user_config')->getUserConfig($username); + if (count($config) > 0) { + // It is done only for existing users + $this->UserName->Text = $config['username']; + $this->UserLongName->Text = $config['long_name']; + $this->UserDescription->Text = $config['description']; + $this->UserEmail->Text = $config['email']; + $this->UserPassword->Text = ''; + $selected_indices = []; + $roles = explode(',', $config['roles']); + for ($i = 0; $i < $this->UserRoles->getItemCount(); $i++) { + if (in_array($this->UserRoles->Items[$i]->Value, $roles)) { + $selected_indices[] = $i; + } + } + $this->UserRoles->setSelectedIndices($selected_indices); + $this->UserAPIHost->SelectedValue = $config['api_hosts']; + $this->UserIps->Text = $config['ips']; + $this->UserEnabled->Checked = ($config['enabled'] == 1); + } + + // It is done both for new user and for edit user + if ($this->isManageUsersAvail()) { + $this->getCallbackClient()->show('user_window_password'); + } else { + $this->getCallbackClient()->hide('user_window_password'); + } + } + + /** + * Save user. + * It works both for new users and for edited users. + * Saves values from modal popup. + * + * @param TCallback $sender sender object + * @param TCallbackEventParameter $param callback parameter + * @return none + */ + public function saveUser($sender, $param) { + if (!$this->UserIps->IsValid) { + // invalid IP restriction value + return; + } + $user_win_type = $this->UserWindowType->Value; + $username = $this->UserName->Text; + $this->getCallbackClient()->hide('user_window_username_exists'); + if ($user_win_type === self::TYPE_ADD_WINDOW) { + $config = $this->getModule('user_config')->getUserConfig($username); + if (count($config) > 0) { + $this->getCallbackClient()->show('user_window_username_exists'); + return; + } + } + + $config = []; + $config['long_name'] = $this->UserLongName->Text; + $config['description'] = $this->UserDescription->Text; + $config['email'] = $this->UserEmail->Text; + + $selected_indices = $this->UserRoles->getSelectedIndices(); + $roles = []; + foreach ($selected_indices as $indice) { + for ($i = 0; $i < $this->UserRoles->getItemCount(); $i++) { + if ($i === $indice) { + $roles[] = $this->UserRoles->Items[$i]->Value; + } + } + } + $config['roles'] = implode(',', $roles); + $config['api_hosts'] = $this->UserAPIHost->SelectedValue; + $config['ips'] = $this->trimIps($this->UserIps->Text); + $config['enabled'] = $this->UserEnabled->Checked ? 1 : 0; + $result = $this->getModule('user_config')->setUserConfig($username, $config); + + // Set password if auth method supports it + if ($result === true && !empty($this->UserPassword->Text) && $this->isManageUsersAvail()) { + // Set Basic auth users password + if ($this->getModule('web_config')->isAuthMethodBasic() && + isset($this->web_config['auth_basic']['user_file'])) { + + $opts = []; + if (isset($this->web_config['auth_basic']['hash_alg'])) { + $opts['hash_alg'] = $this->web_config['auth_basic']['hash_alg']; + } + + // Setting basic users works both for adding and editing users + $basic = $this->getModule('basic_webuser'); + $basic->setUsersConfig( + $username, + $this->UserPassword->Text, + false, + null, + $opts + ); + } + } + + $this->setUserList(null, null); + $this->setRoleList(null, null); + $this->getCallbackClient()->callClientFunction('oUsers.save_user_cb'); + } + + /** + * Remove users action. + * Here is possible to remove one user or many. + * This action is linked with table bulk actions. + * + * @param TCallback $sender sender object + * @param TCallbackEventParameter $param callback parameter + * @return none + */ + public function removeUsers($sender, $param) { + $usernames = explode('|', $param->getCallbackParameter()); + $config = $this->getModule('user_config')->getConfig(); + for ($i = 0; $i < count($usernames); $i++) { + if (key_exists($usernames[$i], $config)) { + unset($config[$usernames[$i]]); + } + } + $result = $this->getModule('user_config')->setConfig($config); + + if ($result === true && $this->isManageUsersAvail() && + $this->getModule('web_config')->isAuthMethodBasic() && + isset($this->web_config['auth_basic']['user_file'])) { + // Remove basic auth users too + $basic = $this->getModule('basic_webuser'); + $basic->removeUsers($usernames); + } + + // refresh user list + $this->setUserList(null, null); + + // refresh role list + $this->setRoleList(null, null); + } + + /** + * Initialize values in role modal window. + * + * @return none + */ + public function initRoleWindow() { + // set role resources + $resources = $this->getModule('page_category')->getCategories(false); + $this->RoleResources->DataSource = array_combine($resources, $resources); + $this->RoleResources->dataBind(); + } + + /** + * Set and load role list. + * + * @param TCallback $sender sender object + * @param TCallbackEventParameter callback parameter + * @return none + */ + public function setRoleList($sender, $param) { + $config = $this->getModule('user_role')->getRoles(); + $this->addUserStatsToRoles($config); + $this->getCallbackClient()->callClientFunction('oRoles.load_role_list_cb', [ + array_values($config) + ]); + } + + /** + * Load data in role modal window. + * + * @param TCallback $sender sender object + * @param TCallbackEventParameter $param callback parameter + * @return none + */ + public function loadRoleWindow($sender, $param) { + $role = $param->getCallbackParameter(); + $config = $this->getModule('user_role')->getRole($role); + if (count($config) > 0) { + // Edit role window + $this->Role->Text = $config['role']; + $this->RoleLongName->Text = $config['long_name']; + $this->RoleDescription->Text = $config['description']; + $selected_indices = []; + $resources = explode(',', $config['resources']); + for ($i = 0; $i < $this->RoleResources->getItemCount(); $i++) { + if (in_array($this->RoleResources->Items[$i]->Value, $resources)) { + $selected_indices[] = $i; + } + } + $this->RoleResources->setSelectedIndices($selected_indices); + $this->RoleEnabled->Checked = ($config['enabled'] == 1); + if ($this->getModule('user_role')->isRolePreDefined($role)) { + $this->RoleSave->Display = 'None'; + $this->PreDefinedRoleMsg->Display = 'Dynamic'; + } else { + $this->RoleSave->Display = 'Dynamic'; + $this->PreDefinedRoleMsg->Display = 'None'; + } + } else { + // New role window + $this->RoleSave->Display = 'Dynamic'; + $this->PreDefinedRoleMsg->Display = 'None'; + } + } + + /** + * Save role. + * It works both for new roles and for edited roles. + * Saves values from modal popup. + * + * @param TCallback $sender sender object + * @param TCallbackEventParameter $param callback parameter + * @return none + */ + public function saveRole($sender, $param) { + $role_win_type = $this->RoleWindowType->Value; + $role = $this->Role->Text; + $this->getCallbackClient()->hide('role_window_role_exists'); + if ($role_win_type === self::TYPE_ADD_WINDOW) { + $config = $this->getModule('user_role')->getRole($role); + if (count($config) > 0) { + $this->getCallbackClient()->show('role_window_role_exists'); + return; + } + } + if ($this->getModule('user_role')->isRolePreDefined($role)) { + // Predefined roles cannot be saved + return; + } + $config = []; + $config['long_name'] = $this->RoleLongName->Text; + $config['description'] = $this->RoleDescription->Text; + + $selected_indices = $this->RoleResources->getSelectedIndices(); + $resources = []; + foreach ($selected_indices as $indice) { + for ($i = 0; $i < $this->RoleResources->getItemCount(); $i++) { + if ($i === $indice) { + $resources[] = $this->RoleResources->Items[$i]->Value; + } + } + } + $config['resources'] = implode(',', $resources); + $config['enabled'] = $this->RoleEnabled->Checked ? 1 : 0; + $this->getModule('role_config')->setRoleConfig($role, $config); + $this->setRoleList(null, null); + if ($role_win_type === self::TYPE_ADD_WINDOW) { + // refresh user window for new role + $this->initUserWindow(); + } + $this->getCallbackClient()->callClientFunction('oRoles.save_role_cb'); + } + + /** + * Remove roles action. + * Here is possible to remove one role or many. + * This action is linked with table bulk actions. + * + * @param TCallback $sender sender object + * @param TCallbackEventParameter $param callback parameter + * @return none + */ + public function removeRoles($sender, $param) { + $roles = explode('|', $param->getCallbackParameter()); + $config = $this->getModule('role_config')->getConfig(); + $user_role = $this->getModule('user_role'); + for ($i = 0; $i < count($roles); $i++) { + if (key_exists($roles[$i], $config)) { + if ($user_role->isRolePreDefined($roles[$i])) { + // Predefined roles cannot be saved + continue; + } + unset($config[$roles[$i]]); + } + } + $this->getModule('role_config')->setConfig($config); + $this->setRoleList(null, null); + // refresh user window to now show removed roles + $this->initUserWindow(); + } + + /** + * Add user statistics to roles. + * It adds user count to information about roles. + * + * @param array $role_config role config (note, passing by reference) + * @return none + */ + private function addUserStatsToRoles(&$role_config) { + $config = []; + if (count($this->user_config) > 0) { + $config = $this->user_config; + } else { + $config = $this->getModule('user_config')->getConfig(); + } + $user_roles = []; + foreach ($role_config as $role => $prop) { + $user_roles[$role] = 0; + } + foreach ($config as $username => $prop) { + $roles = explode(',', $prop['roles']); + for ($i = 0; $i < count($roles); $i++) { + $user_roles[$roles[$i]]++; + } + } + foreach ($role_config as $role => $prop) { + $role_config[$role]['user_count'] = $user_roles[$role]; + } + } + + /** + * Set basic authentication user file. + * + * @return none + */ + private function setBasicAuthConfig() { + if ($this->isManageUsersAvail() && isset($this->web_config['auth_basic']['user_file'])) { + $this->getModule('basic_webuser')->setConfigPath($this->web_config['auth_basic']['user_file']); + } + } + + /** + * Get basic users and provide them to template. + * + * @param TActiveLinkButton $sender sender + * @param TCommandEventParameter $param event parameter object + * @return none + */ + public function getBasicUsers($sender, $param) { + if ($param instanceof Prado\Web\UI\TCommandEventParameter && $param->getCommandParameter() === 'load') { + // reset criteria filters when modal is open + $this->GetUsersImportOptions->SelectedValue = self::IMPORT_OPT_ALL_USERS; + $this->GetUsersCriteria->SelectedValue = self::IMPORT_CRIT_USERNAME; + $this->GetUsersCriteriaFilter->Text = ''; + $this->getCallbackClient()->hide('get_users_criteria'); + $this->getCallbackClient()->hide('get_users_advanced_options'); + + // set role resources + $this->setRoles($this->GetUsersDefaultRole, WebUserRoles::NORMAL); + + // set API hosts + $this->setAPIHosts($this->GetUsersDefaultAPIHost, HostConfig::MAIN_CATALOG_HOST); + } + + $params = $this->getBasicParams(); + + // add additional parameters + $this->addBasicExtraParams($params); + + $pattern = ''; + if (!empty($params['filter_val'])) { + $pattern = '*' . $params['filter_val'] . '*'; + } + + $basic = $this->getModule('basic_webuser'); + // set path from input because user can have unsaved changes + $basic->setConfigPath($this->BasicAuthUserFile->Text); + $users = $basic->getUsers($pattern); + $users = array_keys($users); + $user_list = $this->convertBasicUsers($users); + $this->getCallbackClient()->callClientFunction('oUserSecurity.set_user_table_cb', [ + $user_list + ]); + if (count($users) > 0) { + // Success + $this->TestBasicGetUsersMsg->Text = ''; + $this->TestBasicGetUsersMsg->Display = 'None'; + $this->getCallbackClient()->hide('basic_get_users_error'); + $this->getCallbackClient()->show('basic_get_users_ok'); + } else { + // Error + $this->getCallbackClient()->show('basic_get_users_error'); + $this->TestBasicGetUsersMsg->Text = Prado::localize('Empty user list'); + $this->TestBasicGetUsersMsg->Display = 'Dynamic'; + } + } + + /** + * Convert basic users from simple username list into full form. + * There is option to return user list in config file form or data table form. + * + * @param array $users simple user list + * @param boolean $config_form_result if true, sets the list in config file form + * @return array user list + */ + private function convertBasicUsers(array $users, $config_form_result = false) { + $user_list = []; + for ($i = 0; $i < count($users); $i++) { + $user = [ + 'username' => $users[$i], + 'long_name' => '', + 'email' => '', + 'description' => '' + ]; + if ($config_form_result) { + $user_list[$users[$i]] = $user; + } else { + $user_list[] = $user; + } + } + return $user_list; + } + + /** + * Get basic auth specific parameters with form values. + * + * @return array array basic auth parameters + */ + private function getBasicParams() { + $params = []; + $params['allow_manage_users'] = $this->BasicAuthAllowManageUsers->Checked ? 1 : 0; + $params['user_file'] = $this->BasicAuthUserFile->Text; + $params['hash_alg'] = $this->BasicAuthHashAlgorithm->SelectedValue; + return $params; + } + + /** + * Add to basic auth params additional parameters. + * Note, extra parameters are not set in config. + * + * @param array $params basic auth parameters (passing by reference) + * @return none + */ + private function addBasicExtraParams(&$params) { + if ($this->GetUsersImportOptions->SelectedValue == self::IMPORT_OPT_CRITERIA) { + $params['filter_val'] = $this->GetUsersCriteriaFilter->Text; + } + } + + /** + * Prepare basic users to import. + * + * @param TCallback $sender sender object + * @param TCallbackEventParameter $param event parameter object + * @return array web users list to import + */ + public function prepareBasicUsers($sender, $param) { + $users_web = []; + $import_opt = (integer)$this->GetUsersImportOptions->SelectedValue; + $basic_webuser = $this->getModule('basic_webuser'); + switch ($import_opt) { + case self::IMPORT_OPT_ALL_USERS: { + $users_web = $basic_webuser->getUsers(); + $users_web = array_keys($users_web); + $users_web = $this->convertBasicUsers($users_web, true); + break; + } + case self::IMPORT_OPT_SELECTED_USERS: { + if ($param instanceof Prado\Web\UI\ActiveControls\TCallbackEventParameter) { + $cb_param = $param->getCallbackParameter(); + if (is_array($cb_param)) { + for ($i = 0; $i < count($cb_param); $i++) { + $val = (array)$cb_param[$i]; + $users_web[$val['username']] = $val; + } + } + } + break; + } + case self::IMPORT_OPT_CRITERIA: { + $params = $this->getBasicParams(); + // add additional parameters + $this->addBasicExtraParams($params); + if (!empty($params['filter_val'])) { + $pattern = '*' . $params['filter_val'] . '*'; + $users_web = $basic_webuser->getUsers($pattern); + $users_web = array_keys($users_web); + $users_web = $this->convertBasicUsers($users_web, true); + + } + break; + } + } + return $users_web; + } + + /** + * Test basic user file. + * + * @param TActiveLinkButton $sender sender object + * @param TCallbackEventParameter $param event parameter object + * @return none + */ + public function doBasicUserFileTest($sender, $param) { + $user_file = $this->BasicAuthUserFile->Text; + $msg = ''; + $valid = true; + if (!file_exists($user_file)) { + $valid = false; + $msg = Prado::localize('The user file is not accessible.'); + } else if (!is_readable($user_file)) { + $valid = false; + $msg = Prado::localize('The user file is not readable by web server user.'); + } else if (!is_writeable($user_file)) { + $valid = false; + $msg = Prado::localize('The user file is readable but not writeable by web server user.'); + } + $this->BasicAuthUserFileMsg->Text = $msg; + if ($valid) { + $this->getCallbackClient()->show('basic_auth_user_file_test_ok'); + $this->BasicAuthUserFileMsg->Display = 'None'; + } else { + $this->getCallbackClient()->show('basic_auth_user_file_test_error'); + $this->BasicAuthUserFileMsg->Display = 'Dynamic'; + } + } + + /** + * Get LDAP users and provide them to template. + * + * @param TActiveLinkButton $sender sender + * @param TCommandEventParameter $param event parameter object + * @return none + */ + public function getLdapUsers($sender, $param) { + if ($param instanceof Prado\Web\UI\TCommandEventParameter && $param->getCommandParameter() === 'load') { + // reset criteria filters when modal is open + $this->GetUsersImportOptions->SelectedValue = self::IMPORT_OPT_ALL_USERS; + $this->GetUsersCriteria->SelectedValue = self::IMPORT_CRIT_USERNAME; + $this->GetUsersCriteriaFilter->Text = ''; + $this->getCallbackClient()->hide('get_users_criteria'); + $this->getCallbackClient()->hide('get_users_advanced_options'); + + // set role resources + $this->setRoles($this->GetUsersDefaultRole, WebUserRoles::NORMAL); + + // set API hosts + $this->setAPIHosts($this->GetUsersDefaultAPIHost, HostConfig::MAIN_CATALOG_HOST); + } + + $ldap = $this->getModule('ldap'); + $params = $this->getLdapParams(); + $ldap->setParams($params); + + // add additional parameters + $this->addLdapExtraParams($params); + + $filter = $ldap->getFilter($params['user_attr'], '*'); + if (!empty($params['filter_attr']) && !empty($params['filter_val'])) { + $filter = $ldap->getFilter( + $params['filter_attr'], + '*' . $params['filter_val'] . '*' + ); + } + + $users = $ldap->findUserAttr($filter, $params['attrs']); + $user_list = $this->convertLdapUsers($users, $params); + $this->getCallbackClient()->callClientFunction('oUserSecurity.set_user_table_cb', [ + $user_list + ]); + + if (key_exists('count', $users)) { + // Success + $this->TestLdapGetUsersMsg->Text = ''; + $this->TestLdapGetUsersMsg->Display = 'None'; + $this->getCallbackClient()->show('ldap_get_users_ok'); + } else { + // Error + $this->getCallbackClient()->show('ldap_get_users_error'); + $this->TestLdapGetUsersMsg->Text = $ldap->getLdapError(); + $this->TestLdapGetUsersMsg->Display = 'Dynamic'; + } + } + + /** + * Convert LDAP users from simple username list into full form. + * There is option to return user list in config file form or data table form. + * + * @param array $users simple user list + * @param array $params LDAP specific parameters (@see getLdapParams) + * @param boolean $config_form_result if true, sets the list in config file form + * @return array user list + */ + private function convertLdapUsers(array $users, array $params, $config_form_result = false) { + $user_list = []; + for ($i = 0; $i < $users['count']; $i++) { + if (!key_exists($params['user_attr'], $users[$i])) { + $emsg = "User attribute '{$params['user_attr']}' doesn't exist in LDAP response."; + $this->getModule('logging')->log( + __FUNCTION__, + $emsg, + Logging::CATEGORY_EXTERNAL, + __FILE__, + __LINE__ + ); + continue; + } + $username = $long_name = $email = $desc = ''; + if ($params['user_attr'] !== Ldap::DN_ATTR && $users[$i][$params['user_attr']]['count'] != 1) { + $emsg = "Invalid user attribute count for '{$params['user_attr']}'. Is {$users[$i][$params['user_attr']]['count']}, should be 1."; + $this->getModule('logging')->log( + __FUNCTION__, + $emsg, + Logging::CATEGORY_EXTERNAL, + __FILE__, + __LINE__ + ); + continue; + + } + $username = $users[$i][$params['user_attr']]; + if ($params['user_attr'] !== Ldap::DN_ATTR) { + $username = $users[$i][$params['user_attr']][0]; + } + + if (key_exists($params['long_name_attr'], $users[$i])) { + if ($params['long_name_attr'] === Ldap::DN_ATTR) { + $long_name = $users[$i][$params['long_name_attr']]; + } else if($users[$i][$params['long_name_attr']]['count'] === 1) { + $long_name = $users[$i][$params['long_name_attr']][0]; + } + } + + if (key_exists($params['email_attr'], $users[$i])) { + if ($params['email_attr'] === Ldap::DN_ATTR) { + $email = $users[$i][$params['email_attr']]; + } else if ($users[$i][$params['email_attr']]['count'] === 1) { + $email = $users[$i][$params['email_attr']][0]; + } + } + + if (key_exists($params['desc_attr'], $users[$i])) { + if ($params['desc_attr'] === Ldap::DN_ATTR) { + $desc = $users[$i][$params['desc_attr']]; + } else if ($users[$i][$params['desc_attr']]['count'] === 1) { + $desc = $users[$i][$params['desc_attr']][0]; + } + } + + if ($config_form_result) { + $user_list[$username] = [ + 'long_name' => $long_name, + 'email' => $email, + 'description' => $desc + ]; + } else { + $user_list[] = [ + 'username' => $username, + 'long_name' => $long_name, + 'email' => $email, + 'description' => $desc + ]; + } + } + return $user_list; + } + + + /** + * Get LDAP auth specific parameters with form values. + * + * @return array array LDAP auth parameters + */ + private function getLdapParams() { + $params = []; + $params['address'] = $this->LdapAuthServerAddress->Text; + $params['port'] = $this->LdapAuthServerPort->Text; + $params['ldaps'] = $this->LdapAuthServerLdaps->Checked ? 1 : 0; + $params['protocol_ver'] = $this->LdapAuthServerProtocolVersion->SelectedValue; + $params['base_dn'] = $this->LdapAuthServerBaseDn->Text; + if ($this->LdapAuthMethodAnonymous->Checked) { + $params['auth_method'] = Ldap::AUTH_METHOD_ANON; + } elseif ($this->LdapAuthMethodSimple->Checked) { + $params['auth_method'] = Ldap::AUTH_METHOD_SIMPLE; + } + $params['bind_dn'] = $this->LdapAuthMethodSimpleUsername->Text; + $params['bind_password'] = $this->LdapAuthMethodSimplePassword->Text; + $params['user_attr'] = $this->LdapAttributesUsername->Text; + $params['long_name_attr'] = $this->LdapAttributesLongName->Text; + $params['desc_attr'] = $this->LdapAttributesDescription->Text; + $params['email_attr'] = $this->LdapAttributesEmail->Text; + return $params; + } + + /** + * Add to LDAP auth params additional parameters. + * Note, extra parameters are not set in config. + * + * @param array $params LDAP auth parameters (passing by reference) + * @return none + */ + private function addLdapExtraParams(&$params) { + $params['attrs'] = [$params['user_attr']]; // user attribute is obligatory + if (key_exists('long_name_attr', $params) && !empty($params['long_name_attr'])) { + $params['attrs'][] = $params['long_name_attr']; + } + if (key_exists('email_attr', $params) && !empty($params['email_attr'])) { + $params['attrs'][] = $params['email_attr']; + } + if (key_exists('desc_attr', $params) && !empty($params['desc_attr'])) { + $params['attrs'][] = $params['desc_attr']; + } + if ($this->GetUsersImportOptions->SelectedValue == self::IMPORT_OPT_CRITERIA) { + $crit = intval($this->GetUsersCriteria->SelectedValue); + switch ($crit) { + case self::IMPORT_CRIT_USERNAME: $params['filter_attr'] = $params['user_attr']; break; + case self::IMPORT_CRIT_LONG_NAME: $params['filter_attr'] = $params['long_name_attr']; break; + case self::IMPORT_CRIT_DESCRIPTION: $params['filter_attr'] = $params['desc_attr']; break; + case self::IMPORT_CRIT_EMAIL: $params['filter_attr'] = $params['email_attr']; break; + } + $params['filter_val'] = $this->GetUsersCriteriaFilter->Text; + } + } + + + /** + * Prepare LDAP users to import. + * + * @param TCallback $sender sender object + * @param TCallbackEventParameter $param event parameter object + * @return array web users list to import + */ + private function prepareLdapUsers($sender, $param) { + $ldap = $this->getModule('ldap'); + $params = $this->getLdapParams(); + $ldap->setParams($params); + + // add additional parameters + $this->addLdapExtraParams($params); + + $import_opt = (integer)$this->GetUsersImportOptions->SelectedValue; + + $users_web = []; + switch ($import_opt) { + case self::IMPORT_OPT_ALL_USERS: { + $filter = $ldap->getFilter($params['user_attr'], '*'); + $users_ldap = $ldap->findUserAttr($filter, $params['attrs']); + $users_web = $this->convertLdapUsers($users_ldap, $params, true); + break; + } + case self::IMPORT_OPT_SELECTED_USERS: { + if ($param instanceof Prado\Web\UI\ActiveControls\TCallbackEventParameter) { + $cb_param = $param->getCallbackParameter(); + if (is_array($cb_param)) { + for ($i = 0; $i < count($cb_param); $i++) { + $val = (array)$cb_param[$i]; + $users_web[$val['username']] = $val; + unset($users_web[$val['username']]['username']); + } + } + } + break; + } + case self::IMPORT_OPT_CRITERIA: { + if (!empty($params['filter_attr']) && !empty($params['filter_val'])) { + $filter = $ldap->getFilter( + $params['filter_attr'], + '*' . $params['filter_val'] . '*' + ); + $users_ldap = $ldap->findUserAttr($filter, $params['attrs']); + $users_web = $this->convertLdapUsers($users_ldap, $params, true); + + } + break; + } + } + return $users_web; + } + + /** + * Test LDAP connection. + * + * @param TActiveLinkButton $sender sender object + * @param TCallbackEventParameter $param event object parameter + * @return none + */ + public function testLdapConnection($sender, $param) { + $ldap = $this->getModule('ldap'); + $params = $this->getLdapParams(); + $ldap->setParams($params); + + if ($ldap->adminBind()) { + $this->TestLdapConnectionMsg->Text = ''; + $this->TestLdapConnectionMsg->Display = 'None'; + $this->getCallbackClient()->show('ldap_test_connection_ok'); + } else { + $this->getCallbackClient()->show('ldap_test_connection_error'); + $this->TestLdapConnectionMsg->Text = $ldap->getLdapError(); + $this->TestLdapConnectionMsg->Display = 'Dynamic'; + } + } + + /** + * Main method to import users. + * Supported are basic auth and LDAP auth user imports. + * + * @param TActiveLinkButton $sender sender object + * @param TCallbackEventParameter $param event object parameter + * @return none + */ + public function importUsers($sender, $param) { + if (!$this->GetUsersDefaultIps->IsValid) { + // invalid IP restriction value + return; + } + + $users_web = []; + if ($this->BasicAuth->Checked) { + $users_web = $this->prepareBasicUsers($sender, $param); + } elseif ($this->LdapAuth->Checked) { + $users_web = $this->prepareLdapUsers($sender, $param); + } + + // Get default roles for imported users + $def_roles = $this->GetUsersDefaultRole->getSelectedIndices(); + $role_list = []; + foreach ($def_roles as $indice) { + for ($i = 0; $i < $this->GetUsersDefaultRole->getItemCount(); $i++) { + if ($i === $indice) { + $role_list[] = $this->GetUsersDefaultRole->Items[$i]->Value; + } + } + } + $roles = implode(',', $role_list); + + // Get default API hosts for imported users + $api_hosts = $this->GetUsersDefaultAPIHost->SelectedValue; + + // Get default IP address restrictions for imported users + $ips = $this->trimIps($this->GetUsersDefaultIps->Text); + + // fill missing default values + $add_def_user_params = function (&$user, $idx) use ($roles, $api_hosts, $ips) { + $user['roles'] = $roles; + $user['api_hosts'] = $api_hosts; + $user['ips'] = $ips; + $user['enabled'] = '1'; + }; + array_walk($users_web, $add_def_user_params); + + $user_mod = $this->getModule('user_config'); + $users = $user_mod->getConfig(); + + $users_cfg = []; + if ($this->GetUsersProtectOverwrite->Checked) { + $users_cfg = array_merge($users_web, $users); + } else { + $users_cfg = array_merge($users, $users_web); + } + $user_mod->setConfig($users_cfg); + + // refresh user list + $this->setUserList(null, null); + + // refresh role list + $this->setRoleList(null, null); + + $this->getCallbackClient()->callClientFunction('oUserSecurity.show_user_modal', [ + false + ]); + } + + /** + * Get users and provide them to template. + * + * @param TActiveLinkButton $sender sender object + * @param TCallbackEventParameter $param event object parameter + * @return none + */ + public function getUsers($sender, $param) { + if ($this->BasicAuth->Checked) { + $this->getBasicUsers($sender, $param); + } elseif ($this->LdapAuth->Checked) { + $this->getLdapUsers($sender, $param); + } + } + + /** + * Save security config. + * + * @param TActiveLinkButton $sender sender object + * @param TCallbackEventParameter $param event object parameter + * @return none + */ + public function saveSecurityConfig($sender, $param) { + $config = $this->web_config; + if (!key_exists('security', $config)) { + $config['security'] = []; + } + if ($this->GeneralDefaultNoAccess->Checked) { + $config['security']['def_access'] = WebConfig::DEF_ACCESS_NO_ACCESS; + } elseif ($this->GeneralDefaultAccess->Checked) { + $config['security']['def_access'] = WebConfig::DEF_ACCESS_DEFAULT_SETTINGS; + $config['security']['def_role'] = $this->GeneralDefaultAccessRole->SelectedValue; + $config['security']['def_api_host'] = $this->GeneralDefaultAccessAPIHost->SelectedValue; + } + if ($this->BasicAuth->Checked) { + $config['security']['auth_method'] = WebConfig::AUTH_METHOD_BASIC; + $config['auth_basic'] = $this->getBasicParams(); + } else if ($this->LdapAuth->Checked) { + $config['security']['auth_method'] = WebConfig::AUTH_METHOD_LDAP; + $config['auth_ldap'] = $this->getLdapParams(); + } + $ret = $this->getModule('web_config')->setConfig($config); + if ($ret === true) { + $this->getCallbackClient()->hide('auth_method_save_error'); + $this->getCallbackClient()->show('auth_method_save_ok'); + } else { + $this->getCallbackClient()->hide('auth_method_save_ok'); + $this->getCallbackClient()->show('auth_method_save_error'); + } + } + + /** + * Determines if user management is enabled. + * This checking bases on selected auth method and permission to manage users. + * + * @return boolean true if managing users is enabled, otherwise false + */ + private function isManageUsersAvail() { + $is_basic = $this->getModule('web_config')->isAuthMethodBasic(); + $allow_manage_users = (isset($this->web_config['auth_basic']['allow_manage_users']) && + $this->web_config['auth_basic']['allow_manage_users'] == 1); + return ($is_basic && $allow_manage_users); + } + + /** + * Validate IP restriction address value. + * + * @param TActiveCustomValidator $sender sender object + * @param TServerValidateEventParameter $param event object parameter + * @return none + */ + public function validateIps($sender, $param) { + $valid = true; + $val = trim($param->Value); + if (!empty($val)) { + $ips = explode(',', $val); + for ($i = 0; $i < count($ips); $i++) { + $ip = trim($ips[$i]); + if (!filter_var($ip, FILTER_VALIDATE_IP) && !(strpos($ip, '*') !== false && preg_match('/^[\da-f:.*]+$/i', $ip) === 1)) { + $valid = false; + break; + } + } + } + $param->IsValid = $valid; + } + + /** + * Simple helper that trims IP restriction address values. + * + * @param string $ips IP restriction address values + * @return string trimmed addresses + */ + public function trimIps($ips) { + $ips = trim($ips); + if (!empty($ips)) { + $ips = explode(',', $ips); + $ips = array_map('trim', $ips); + $ips = implode(',', $ips); + } + return $ips; + } +} +?> diff --git a/gui/baculum/protected/Web/Pages/StatisticsList.php b/gui/baculum/protected/Web/Pages/StatisticsList.php index e36e82408..a6ee4ef78 100644 --- a/gui/baculum/protected/Web/Pages/StatisticsList.php +++ b/gui/baculum/protected/Web/Pages/StatisticsList.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -33,8 +33,6 @@ class StatisticsList extends BaculumWebPage { const USE_CACHE = true; - protected $admin = true; - public $statistics = array(); public function onInit($param) { diff --git a/gui/baculum/protected/Web/Pages/StatisticsView.php b/gui/baculum/protected/Web/Pages/StatisticsView.php index 51abd690c..a8234a77e 100644 --- a/gui/baculum/protected/Web/Pages/StatisticsView.php +++ b/gui/baculum/protected/Web/Pages/StatisticsView.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -34,8 +34,6 @@ class StatisticsView extends BaculumWebPage { const COMPONENT_TYPE = 'ComponentType'; const STATISTICS_NAME = 'StatisticsName'; - protected $admin = true; - public function onInit($param) { parent::onInit($param); if ($this->IsPostBack || $this->IsCallBack) { diff --git a/gui/baculum/protected/Web/Pages/StorageList.php b/gui/baculum/protected/Web/Pages/StorageList.php index 3aa29e317..67bae0fb9 100644 --- a/gui/baculum/protected/Web/Pages/StorageList.php +++ b/gui/baculum/protected/Web/Pages/StorageList.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -33,8 +33,6 @@ class StorageList extends BaculumWebPage { const USE_CACHE = true; - protected $admin = true; - public $storages; public function onInit($param) { diff --git a/gui/baculum/protected/Web/Pages/StorageView.php b/gui/baculum/protected/Web/Pages/StorageView.php index 3613f39cd..45bb7c71f 100644 --- a/gui/baculum/protected/Web/Pages/StorageView.php +++ b/gui/baculum/protected/Web/Pages/StorageView.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -43,8 +43,6 @@ class StorageView extends BaculumWebPage { const USE_CACHE = true; - protected $admin = true; - public function onInit($param) { parent::onInit($param); if ($this->IsPostBack || $this->IsCallBack) { diff --git a/gui/baculum/protected/Web/Pages/Users.page b/gui/baculum/protected/Web/Pages/Users.page deleted file mode 100644 index 13f85fa03..000000000 --- a/gui/baculum/protected/Web/Pages/Users.page +++ /dev/null @@ -1,129 +0,0 @@ -<%@ MasterClass="Application.Web.Layouts.Main" Theme="Baculum-v2"%> - - -
-
- <%[ Users ]%> -
-
- - - - - - - - - - - - - - - - - - - - - - - -
<%[ User name ]%><%[ Role ]%><%[ API host ]%><%[ Actions ]%>
<%=$this->Data['user']%><%=$this->Data['admin'] ? Prado::localize('Administrator') : Prado::localize('Normal user')%> - - Main - - - -   - Data['admin'] ? 'style="visibility: hidden"' : ''%> onclick="Users.rmUser('<%=$this->Data['user']%>')"> -  <%[ Remove user ]%> - - -  <%[ Change password ]%> - - - - -
- -
- - - - -
diff --git a/gui/baculum/protected/Web/Pages/Users.php b/gui/baculum/protected/Web/Pages/Users.php deleted file mode 100644 index 2b73b3487..000000000 --- a/gui/baculum/protected/Web/Pages/Users.php +++ /dev/null @@ -1,114 +0,0 @@ - - * @category Page - * @package Baculum Web - */ -class Users extends BaculumWebPage { - - protected $admin = true; - - public function setUsers() { - $all_users = $this->getModule('basic_webuser')->getAllUsers(); - $users = array_keys($all_users); - sort($users); - $users_list = array(); - $users_feature = (array_key_exists('users', $this->web_config) && is_array($this->web_config['users'])); - for ($i = 0; $i < count($users); $i++) { - $host = null; - if ($users_feature && array_key_exists($users[$i], $this->web_config['users'])) { - $host = $this->web_config['users'][$users[$i]]; - } - $users_list[] = array( - 'user' => $users[$i], - 'host' => $host, - 'admin' => ($users[$i] === $this->web_config['baculum']['login']) - ); - } - $this->UsersList->dataSource = $users_list; - $this->UsersList->dataBind(); - } - - public function initHosts($sender, $param) { - $api_hosts = array_keys($this->getModule('host_config')->getConfig()); - $sender->DataSource = array_combine($api_hosts, $api_hosts); - $sender->dataBind(); - } - - public function userAction($sender, $param) { - $this->UsersList->ActiveControl->EnableUpdate = true; - list($action, $user, $value) = explode(';', $param->CallbackParameter, 3); - switch($action) { - case 'newuser': - case 'chpwd': { - $this->getModule('basic_webuser')->setUsersConfig($user, $value); - if ($user === $this->web_config['baculum']['login']) { - // if admin password changed then try to auto-login by async request - $http_protocol = isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) ? 'https' : 'http'; - $this->switchToUser($user, $value); - exit(); - } else { - // if normal user's password changed then update users grid - $this->setUsers(); - } - } - break; - case 'rmuser': { - if ($user != $_SERVER['PHP_AUTH_USER']) { - $this->getModule('basic_webuser')->removeUser($user); - if (array_key_exists('users', $this->web_config) && array_key_exists($user, $this->web_config['users'])) { - unset($this->web_config['users'][$user]); - } - $this->getModule('web_config')->setConfig($this->web_config); - $this->setUsers(); - } - break; - } - case 'set_host': { - if (empty($value) && array_key_exists($user, $this->web_config['users'])) { - unset($this->web_config['users'][$user]); - } else { - $this->web_config['users'][$user] = $value; - } - $this->getModule('web_config')->setConfig($this->web_config); - break; - } - } - } - - public function isSelectedHost($user, $host) { - if (!key_exists('users', $this->web_config)) { - return; - } - return (key_exists($user, $this->web_config['users']) && $this->web_config['users'][$user] === $host); - } -} -?> diff --git a/gui/baculum/protected/Web/Pages/VolumeList.php b/gui/baculum/protected/Web/Pages/VolumeList.php index f0b731e09..a1b93fddd 100644 --- a/gui/baculum/protected/Web/Pages/VolumeList.php +++ b/gui/baculum/protected/Web/Pages/VolumeList.php @@ -33,8 +33,6 @@ class VolumeList extends BaculumWebPage { const USE_CACHE = true; - protected $admin = true; - public $volumes; public function onInit($param) { @@ -42,9 +40,7 @@ class VolumeList extends BaculumWebPage { if ($this->IsPostBack || $this->IsCallBack) { return; } - if ($_SESSION['admin']) { - $this->setVolumes(); - } + $this->setVolumes(); } public function setVolumes() { diff --git a/gui/baculum/protected/Web/Pages/VolumeView.php b/gui/baculum/protected/Web/Pages/VolumeView.php index 547932554..a6c87d50e 100644 --- a/gui/baculum/protected/Web/Pages/VolumeView.php +++ b/gui/baculum/protected/Web/Pages/VolumeView.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -41,8 +41,6 @@ class VolumeView extends BaculumWebPage { const MEDIAID = 'MediaId'; const VOLUME_NAME = 'VolumeName'; - protected $admin = true; - public $jobs_on_volume; private $volstatus_by_dir = array('Recycle', 'Purged', 'Error', 'Busy'); diff --git a/gui/baculum/protected/Web/Pages/WebConfigWizard.page b/gui/baculum/protected/Web/Pages/WebConfigWizard.page index 71b3cdb49..dc0c729c6 100644 --- a/gui/baculum/protected/Web/Pages/WebConfigWizard.page +++ b/gui/baculum/protected/Web/Pages/WebConfigWizard.page @@ -7,32 +7,34 @@ NavigationStyle.CssClass="navigation" UseDefaultLayout="false" ShowSideBar="false" + OnPreviousButtonClick="previousStep" + OnNextButtonClick="nextStep" OnCancelButtonClick="wizardStop" OnCompleteButtonClick="wizardCompleted" > -
+

-
+

-
+
SourceTemplateControl->first_run ? ' style="display: none"' : ''%>>

-
+
@@ -169,7 +171,7 @@
- <%[ Authorization to Baculum API ]%> + <%[ Access to Baculum API ]%>
<%[ Protocol: ]%>
<%=$this->AddNewHost->APIProtocol->SelectedValue%>
@@ -219,8 +221,8 @@
-
- <%[ Authorization to Baculum Web ]%> + SourceTemplateControl->first_run ? ' style="display: none"' : ''%>> + <%[ Access to Baculum Web ]%>
<%[ Administration login: ]%>
<%=$this->WebLogin->Text%>
diff --git a/gui/baculum/protected/Web/Pages/WebConfigWizard.php b/gui/baculum/protected/Web/Pages/WebConfigWizard.php index 5384f2f1e..56c2a84df 100644 --- a/gui/baculum/protected/Web/Pages/WebConfigWizard.php +++ b/gui/baculum/protected/Web/Pages/WebConfigWizard.php @@ -34,21 +34,14 @@ Prado::using('System.Web.UI.ActiveControls.TActiveDropDownList'); */ class WebConfigWizard extends BaculumWebPage { - - protected $admin = false; - public $first_run; public $host_config; public function onInit($param) { parent::onInit($param); - $this->Lang->SelectedValue = $this->getLanguage(); $this->host_config = $this->getModule('host_config')->getConfig(); $this->first_run = (count($this->host_config) == 0 || !key_exists(HostConfig::MAIN_CATALOG_HOST, $this->host_config)); Logging::$debug_enabled = Logging::$debug_enabled ?: $this->first_run; - if($this->first_run === false && !$_SESSION['admin']) { - parent::accessDenied(); - } } public function onLoad($param) { @@ -75,7 +68,6 @@ class WebConfigWizard extends BaculumWebPage $this->AddNewHost->APIOAuth2RedirectURI->Text = $this->host_config[$host]['redirect_uri']; $this->AddNewHost->APIOAuth2Scope->Text = $this->host_config[$host]['scope']; } - $this->WebLogin->Text = $this->web_config['baculum']['login']; } else { $this->AddNewHost->APIProtocol->SelectedValue = 'http'; $this->AddNewHost->APIAddress->Text = 'localhost'; @@ -84,10 +76,21 @@ class WebConfigWizard extends BaculumWebPage } } - public function NextStep($sender, $param) { + public function onPreRender($param) { + parent::onPreRender($param); + if($this->IsPostBack || $this->IsCallBack) { + return; + } + $this->Lang->SelectedValue = $this->getModule('web_config')->getLanguage(); + } + + public function nextStep($sender, $param) { + if ($param->CurrentStepIndex === 1 && !$this->first_run) { + $this->InstallWizard->ActiveStepIndex = 3; + } } - public function PreviousStep($sender, $param) { + public function previousStep($sender, $param) { } public function wizardStop($sender, $param) { @@ -124,28 +127,70 @@ class WebConfigWizard extends BaculumWebPage $host_config[$host] = $cfg_host; $ret = $this->getModule('host_config')->setConfig($host_config); if($ret === true) { - $cfg_web = array('baculum' => array(), 'users' => array()); - if (count($this->web_config) > 0) { - $cfg_web = $this->web_config; - } - $cfg_web['baculum']['login'] = $this->WebLogin->Text; - $cfg_web['baculum']['debug'] = 0; - $cfg_web['baculum']['lang'] = $this->Lang->SelectedValue; - if (array_key_exists('users', $cfg_web) && array_key_exists($this->WebLogin->Text, $cfg_web)) { - // Admin shoudn't be added to users section, only regular users - unset($cfg_web['users'][$this->WebLogin->Text]); - } - $ret = $this->getModule('web_config')->setConfig($cfg_web); - if($ret && $this->getModule('basic_webuser')->isUsersConfig() === true) { - $previous_user = $this->first_run ? parent::DEFAULT_AUTH_USER : $this->web_config['baculum']['login']; - $this->getModule('basic_webuser')->setUsersConfig( - $cfg_web['baculum']['login'], + // complete new Baculum main settings + $web_config = $this->getModule('web_config'); + $ret = $web_config->setDefConfigOpts([ + 'baculum' => [ + 'lang' => $this->Lang->SelectedValue + ] + ]); + + $basic_webuser = $this->getModule('basic_webuser'); + if($this->first_run && $ret && $web_config->isAuthMethodBasic()) { + // set new user on first wizard run + $previous_user = parent::DEFAULT_AUTH_USER; + $ret = $basic_webuser->setUsersConfig( + $this->WebLogin->Text, $this->WebPassword->Text, false, $previous_user ); + } else { + $emsg = 'Error while saving basic user config.'; + $this->getModule('logging')->log( + __FUNCTION__, + $emsg, + Logging::CATEGORY_APPLICATION, + __FILE__, + __LINE__ + ); } + + if ($this->first_run && $ret) { + // create new Baculum user on first wizard run + $user_config = $this->getModule('user_config'); + $new_user_prop = $user_config->getUserConfigProps([ + 'username' => $this->WebLogin->Text, + 'roles' => WebUserRoles::ADMIN, + 'enabled' => 1 + ]); + $ret = $user_config->setUserConfig($this->WebLogin->Text, $new_user_prop); + if (!$ret) { + $emsg = 'Error while saving user config.'; + $this->getModule('logging')->log( + __FUNCTION__, + $emsg, + Logging::CATEGORY_APPLICATION, + __FILE__, + __LINE__ + ); + } + + // Login user with new parameters + $this->getModule('auth')->login($this->WebLogin->Text, $this->WebPassword->Text); + } + + // Go to default user page $this->goToDefaultPage(); + } else { + $emsg = 'Error while saving auth host config.'; + $this->getModule('logging')->log( + __FUNCTION__, + $emsg, + Logging::CATEGORY_APPLICATION, + __FILE__, + __LINE__ + ); } } @@ -158,7 +203,7 @@ class WebConfigWizard extends BaculumWebPage } public function setLang($sender, $param) { - $_SESSION['language'] = $sender->SelectedValue; + $this->getModule('web_config')->setLanguage($sender->SelectedValue); } public function validateAdministratorPassword($sender, $param) { diff --git a/gui/baculum/protected/Web/Pages/config.xml b/gui/baculum/protected/Web/Pages/config.xml index 21babe7b7..5a9b90eb6 100644 --- a/gui/baculum/protected/Web/Pages/config.xml +++ b/gui/baculum/protected/Web/Pages/config.xml @@ -6,6 +6,8 @@ + + @@ -19,5 +21,9 @@ + + + + diff --git a/gui/baculum/protected/Web/Portlets/BaculaHosts.php b/gui/baculum/protected/Web/Portlets/BaculaHosts.php index 41e58b003..54456831d 100644 --- a/gui/baculum/protected/Web/Portlets/BaculaHosts.php +++ b/gui/baculum/protected/Web/Portlets/BaculaHosts.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -39,9 +39,6 @@ class BaculaHosts extends HostListTemplate { public $config; public function loadConfig($sender, $param) { - if(!$_SESSION['admin']) { - return; - } $this->config = $this->getModule('host_config')->getConfig(); $hosts = array_keys($this->config); $this->RepeaterHosts->DataSource = $hosts; @@ -73,9 +70,6 @@ class BaculaHosts extends HostListTemplate { } public function removeHost($sender, $param) { - if(!$_SESSION['admin']) { - return; - } $host = $param->getCommandParameter(); if (!empty($host)) { $host_config = $this->getModule('host_config'); diff --git a/gui/baculum/protected/Web/Portlets/DirectiveListBox.tpl b/gui/baculum/protected/Web/Portlets/DirectiveListBox.tpl index 5172e3832..7e8307d59 100644 --- a/gui/baculum/protected/Web/Portlets/DirectiveListBox.tpl +++ b/gui/baculum/protected/Web/Portlets/DirectiveListBox.tpl @@ -20,6 +20,6 @@ Text="Field required." Enabled="<%=$this->getRequired() && $this->getShow()%>" /> -

<%[ Use Ctrl + Mouse Click to change selection ]%>

+

<%[ Use CTRL + left-click to multiple item selection ]%>

diff --git a/gui/baculum/protected/Web/Portlets/MainSideBar.php b/gui/baculum/protected/Web/Portlets/MainSideBar.php index 8128f7bd8..ac43a1a17 100644 --- a/gui/baculum/protected/Web/Portlets/MainSideBar.php +++ b/gui/baculum/protected/Web/Portlets/MainSideBar.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -32,10 +32,33 @@ Prado::using('Application.Web.Portlets.Portlets'); */ class MainSideBar extends Portlets { + /** + * Reload URL is used to refresh page after logout with Basic auth. + */ + public $reload_url = ''; + + public function onInit($param) { + parent::onInit($param); + if ($this->getModule('web_config')->isAuthMethodBasic()) { + $fake_pwd = $this->getModule('crypto')->getRandomString(); + $user = $_SERVER['PHP_AUTH_USER'] . '1'; // must be different than currently logged in Basic user + + // do a login try with different user and password to logout current user + $this->reload_url = $this->getPage()->getFullLoginUrl($user, $fake_pwd); + } + } + public function logout($sender, $param) { - $fake_pwd = $this->getModule('misc')->getRandomString(); - $this->getPage()->switchToUser($_SERVER['PHP_AUTH_USER'], $fake_pwd); - exit(); + $this->getModule('auth')->logout(); + if ($this->getModule('web_config')->isAuthMethodBasic()) { + /** + * This status code 401 is necessary to stop comming AJAX requests + * and to bring the login prompt on. + */ + $this->Response->setStatusCode(401); + } else { + $this->getPage()->goToDefaultPage(); + } } } ?> diff --git a/gui/baculum/protected/Web/Portlets/MainSideBar.tpl b/gui/baculum/protected/Web/Portlets/MainSideBar.tpl index 0ac1d8987..9393060a4 100644 --- a/gui/baculum/protected/Web/Portlets/MainSideBar.tpl +++ b/gui/baculum/protected/Web/Portlets/MainSideBar.tpl @@ -5,40 +5,50 @@
- <%[ Welcome ]%>, <%=$_SERVER['PHP_AUTH_USER']%>
+ <%[ Welcome ]%>, <%=$this->User->getUsername()%>
+ + + if (!window.chrome || window.navigator.webdriver) { + window.location.href = main_side_bar_reload_url; + } else if (window.chrome) { + // For chrome this reload is required to show login Basic auth prompt + window.location.reload(); + } + - - + +

-
+
Baculum Menu
diff --git a/gui/baculum/protected/Web/Portlets/RunJob.php b/gui/baculum/protected/Web/Portlets/RunJob.php index 3bff836da..006e25594 100644 --- a/gui/baculum/protected/Web/Portlets/RunJob.php +++ b/gui/baculum/protected/Web/Portlets/RunJob.php @@ -213,6 +213,7 @@ class RunJob extends Portlets { $priority = $jobdata->priorjobid; } $this->Priority->Text = $priority; + $this->Estimate->Enabled = false; } public function selectJobValues($sender, $param) { diff --git a/gui/baculum/protected/Web/Portlets/Users.php b/gui/baculum/protected/Web/Portlets/Users.php deleted file mode 100644 index 04e16274f..000000000 --- a/gui/baculum/protected/Web/Portlets/Users.php +++ /dev/null @@ -1,124 +0,0 @@ - - * @category Control - * @package Baculum Web - */ -class Users extends Portlets { - - public $web_config; - - public function __construct() { - parent::__construct(); - $this->web_config = $this->getModule('web_config')->getConfig(); - } - - public function setUsers() { - if(!$_SESSION['admin']) { - return; - } - $all_users = $this->getModule('basic_webuser')->getAllUsers(); - $users = array_keys($all_users); - sort($users); - $users_list = array(); - $users_feature = (array_key_exists('users', $this->web_config) && is_array($this->web_config['users'])); - for ($i = 0; $i < count($users); $i++) { - $host = null; - if ($users_feature && array_key_exists($users[$i], $this->web_config['users'])) { - $host = $this->web_config['users'][$users[$i]]; - } - $users_list[] = array( - 'user' => $users[$i], - 'host' => $host, - 'admin' => ($users[$i] === $this->web_config['baculum']['login']) - ); - } - $this->UsersList->dataSource = $users_list; - $this->UsersList->dataBind(); - } - - public function initHosts($sender, $param) { - $api_hosts = array_keys($this->getModule('host_config')->getConfig()); - $sender->DataSource = array_combine($api_hosts, $api_hosts); - $sender->dataBind(); - } - - public function userAction($sender, $param) { - if(!$_SESSION['admin']) { - return; - } - $this->UsersList->ActiveControl->EnableUpdate = true; - list($action, $user, $value) = explode(';', $param->CallbackParameter, 3); - switch($action) { - case 'newuser': - case 'chpwd': { - $admin = false; - $valid = true; - if ($user === $this->web_config['baculum']['login']) { - $this->web_config['baculum']['password'] = $value; - $valid = $this->getModule('web_config')->setConfig($this->web_config); - $admin = true; - } - if ($valid === true) { - $this->getModule('basic_webuser')->setUsersConfig($user, $value); - } - if ($admin === true) { - // if admin password changed then try to auto-login by async request - $http_protocol = isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) ? 'https' : 'http'; - $this->switchToUser($user, $value); - exit(); - } else { - // if normal user's password changed then update users grid - $this->setUsers(); - } - } - break; - case 'rmuser': { - if ($user != $_SERVER['PHP_AUTH_USER']) { - $this->getModule('basic_webuser')->removeUser($user); - if (array_key_exists('users', $this->web_config) && array_key_exists($user, $this->web_config['users'])) { - unset($this->web_config['users'][$user]); - } - $this->getModule('web_config')->setConfig($this->web_config); - $this->setUsers(); - } - break; - } - case 'set_host': { - if (empty($value) && array_key_exists($user, $this->web_config['users'])) { - unset($this->web_config['users'][$user]); - } else { - $this->web_config['users'][$user] = $value; - } - $this->getModule('web_config')->setConfig($this->web_config); - break; - } - } - } -} diff --git a/gui/baculum/protected/Web/Portlets/Users.tpl b/gui/baculum/protected/Web/Portlets/Users.tpl deleted file mode 100644 index 9833b263d..000000000 --- a/gui/baculum/protected/Web/Portlets/Users.tpl +++ /dev/null @@ -1,84 +0,0 @@ -Add<%[ Add new user ]%> - - - - - - - - - - - - - - - - - - - - -
<%[ User name ]%><%[ Role ]%><%[ API host ]%><%[ Actions ]%>
<%=$this->Data['user']%><%=$this->Data['admin'] ? Prado::localize('Administrator') : Prado::localize('Normal user')%> - - Main - - - - - - - Data['admin'] ? 'style="visibility: hidden"' : ''%> onclick="Users.rmUser('<%=$this->Data['user']%>')"> <%[ Remove user ]%> - - - <%[ Change password ]%> - - - - - <%[ Save ]%> - - - <%[ Close ]%> - - -
- -
- - diff --git a/gui/baculum/protected/Web/endpoints.xml b/gui/baculum/protected/Web/endpoints.xml index 41d2d64be..9673c8e19 100644 --- a/gui/baculum/protected/Web/endpoints.xml +++ b/gui/baculum/protected/Web/endpoints.xml @@ -1,6 +1,7 @@ + @@ -29,7 +30,7 @@ - + diff --git a/gui/baculum/protected/application.xml b/gui/baculum/protected/application.xml index 221eb2207..ff55430f3 100644 --- a/gui/baculum/protected/application.xml +++ b/gui/baculum/protected/application.xml @@ -15,6 +15,15 @@ + + + + + + + + + diff --git a/gui/baculum/themes/Baculum-v2/css/baculum.css b/gui/baculum/themes/Baculum-v2/css/baculum.css index 430d44436..f944d9a2d 100644 --- a/gui/baculum/themes/Baculum-v2/css/baculum.css +++ b/gui/baculum/themes/Baculum-v2/css/baculum.css @@ -345,3 +345,23 @@ table.component td:nth-of-type(1) { .info { color: #333; } + +.error { + color: red; +} + +/* Option row */ +.opt_row { + line-height: 36px; +} + +/* Required option */ +.opt_req { + line-height: 40px; + display: inline; + margin-left: 2px; +} + +.field_invalid { + border: 1px solid red !important; +} diff --git a/gui/baculum/themes/Baculum-v2/logo_xl.png b/gui/baculum/themes/Baculum-v2/logo_xl.png new file mode 100644 index 000000000..fc75c2ec8 Binary files /dev/null and b/gui/baculum/themes/Baculum-v2/logo_xl.png differ